Authentification stable - WIP

This commit is contained in:
DahoudG
2025-09-19 12:35:46 +00:00
parent 63fe107f98
commit 098894bdc1
383 changed files with 13072 additions and 93334 deletions

View File

@@ -0,0 +1,418 @@
/// Dashboard Adaptatif Principal - Orchestrateur Intelligent
/// Sélectionne et affiche le dashboard approprié selon le rôle utilisateur
library adaptive_dashboard_page;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/auth/bloc/auth_bloc.dart';
import '../../../../core/auth/models/user_role.dart';
import '../../../../core/widgets/adaptive_widget.dart';
import 'role_dashboards/super_admin_dashboard.dart';
import 'role_dashboards/org_admin_dashboard.dart';
import 'role_dashboards/moderator_dashboard.dart';
import 'role_dashboards/active_member_dashboard.dart';
import 'role_dashboards/simple_member_dashboard.dart';
import 'role_dashboards/visitor_dashboard.dart';
/// Page Dashboard Adaptatif - Le cœur du système morphique
///
/// Cette page utilise l'AdaptiveWidget pour afficher automatiquement
/// le dashboard approprié selon le rôle de l'utilisateur connecté.
///
/// Fonctionnalités :
/// - Morphing automatique entre les dashboards
/// - Animations fluides lors des changements de rôle
/// - Gestion des états de chargement et d'erreur
/// - Fallback gracieux pour les rôles non supportés
class AdaptiveDashboardPage extends StatefulWidget {
const AdaptiveDashboardPage({super.key});
@override
State<AdaptiveDashboardPage> createState() => _AdaptiveDashboardPageState();
}
class _AdaptiveDashboardPageState extends State<AdaptiveDashboardPage>
with TickerProviderStateMixin {
/// Contrôleur d'animation pour les transitions
late AnimationController _transitionController;
/// Animation de fade pour les transitions
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_initializeAnimations();
}
@override
void dispose() {
_transitionController.dispose();
super.dispose();
}
/// Initialise les animations de transition
void _initializeAnimations() {
_transitionController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _transitionController,
curve: Curves.easeInOutCubic,
));
// Démarrer l'animation initiale
_transitionController.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
// Déclencher l'animation lors des changements d'état
if (state is AuthAuthenticated) {
_transitionController.reset();
_transitionController.forward();
}
},
child: AnimatedBuilder(
animation: _fadeAnimation,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: _buildAdaptiveDashboard(),
);
},
),
),
);
}
/// Construit le dashboard adaptatif selon le rôle
Widget _buildAdaptiveDashboard() {
return AdaptiveWidget(
// Mapping des rôles vers leurs dashboards spécifiques
roleWidgets: {
UserRole.superAdmin: () => const SuperAdminDashboard(),
UserRole.orgAdmin: () => const OrgAdminDashboard(),
UserRole.moderator: () => const ModeratorDashboard(),
UserRole.activeMember: () => const ActiveMemberDashboard(),
UserRole.simpleMember: () => const SimpleMemberDashboard(),
UserRole.visitor: () => const VisitorDashboard(),
},
// Permissions requises pour accéder au dashboard
requiredPermissions: const [
'dashboard.view.own',
],
// Widget affiché si les permissions sont insuffisantes
fallbackWidget: _buildUnauthorizedDashboard(),
// Widget affiché pendant le chargement
loadingWidget: _buildLoadingDashboard(),
// Configuration des animations
enableMorphing: true,
morphingDuration: const Duration(milliseconds: 800),
animationCurve: Curves.easeInOutCubic,
// Audit trail activé
auditLog: true,
);
}
/// Dashboard affiché en cas d'accès non autorisé
Widget _buildUnauthorizedDashboard() {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFF8F9FA),
Color(0xFFE9ECEF),
],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icône d'accès refusé
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(60),
),
child: const Icon(
Icons.lock_outline,
size: 60,
color: Colors.red,
),
),
const SizedBox(height: 32),
// Titre
Text(
'Accès Non Autorisé',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
const SizedBox(height: 16),
// Description
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Vous n\'avez pas les permissions nécessaires pour accéder au dashboard. Veuillez contacter un administrateur.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.grey[600],
),
),
),
const SizedBox(height: 32),
// Bouton de contact
ElevatedButton.icon(
onPressed: () => _onContactSupport(),
icon: const Icon(Icons.support_agent),
label: const Text('Contacter le Support'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
),
),
],
),
),
),
);
}
/// Dashboard affiché pendant le chargement
Widget _buildLoadingDashboard() {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF6C5CE7),
Color(0xFF5A4FCF),
],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo animé
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(seconds: 2),
builder: (context, value, child) {
return Transform.rotate(
angle: value * 2 * 3.14159,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(40),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: const Icon(
Icons.dashboard,
color: Colors.white,
size: 40,
),
),
);
},
),
const SizedBox(height: 32),
// Titre
Text(
'UnionFlow',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Indicateur de chargement
const SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
strokeWidth: 3,
),
),
const SizedBox(height: 16),
// Message de chargement
Text(
'Préparation de votre dashboard...',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
),
);
}
/// Gère le contact avec le support
void _onContactSupport() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Contacter le Support'),
content: const Text(
'Pour obtenir de l\'aide, veuillez envoyer un email à :\n\nsupport@unionflow.com\n\nOu appelez le :\n+33 1 23 45 67 89',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
// Ici, on pourrait ouvrir l'app email ou téléphone
},
child: const Text('Envoyer Email'),
),
],
),
);
}
}
/// Extension pour faciliter la navigation vers le dashboard adaptatif
extension AdaptiveDashboardNavigation on BuildContext {
/// Navigue vers le dashboard adaptatif
void navigateToAdaptiveDashboard() {
Navigator.of(this).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const AdaptiveDashboardPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 0.1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOutCubic,
)),
child: child,
),
);
},
transitionDuration: const Duration(milliseconds: 600),
),
);
}
}
/// Mixin pour les dashboards qui ont besoin de fonctionnalités communes
mixin DashboardMixin<T extends StatefulWidget> on State<T> {
/// Affiche une notification de succès
void showSuccessNotification(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
/// Affiche une notification d'erreur
void showErrorNotification(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
}
/// Affiche une boîte de dialogue de confirmation
Future<bool> showConfirmationDialog(String title, String message) async {
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Confirmer'),
),
],
),
);
return result ?? false;
}
}

View File

@@ -1,110 +1,270 @@
import 'package:flutter/material.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../core/animations/page_transitions.dart';
import '../../../demo/presentation/pages/animations_demo_page.dart';
import '../../../debug/debug_api_test_page.dart';
import '../../../performance/presentation/pages/performance_demo_page.dart';
// Imports des nouveaux widgets refactorisés
import '../widgets/welcome/welcome_section_widget.dart';
import '../widgets/kpi/kpi_cards_widget.dart';
import '../widgets/actions/quick_actions_widget.dart';
import '../widgets/activities/recent_activities_widget.dart';
import '../widgets/charts/charts_analytics_widget.dart';
// Import de l'architecture unifiée pour amélioration progressive
import '../../../../shared/widgets/common/unified_page_layout.dart';
/// Page principale du tableau de bord UnionFlow
///
/// Affiche une vue d'ensemble complète de l'association avec :
/// - Section d'accueil personnalisée
/// - Indicateurs clés de performance (KPI)
/// - Actions rapides et gestion
/// - Flux d'activités en temps réel
/// - Analyses et tendances graphiques
///
/// Architecture modulaire avec widgets réutilisables pour une
/// maintenabilité optimale et une évolutivité facilitée.
class DashboardPage extends StatelessWidget {
/// Page principale du tableau de bord - Version simple
class DashboardPage extends StatefulWidget {
const DashboardPage({super.key});
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
@override
Widget build(BuildContext context) {
// Utilisation de UnifiedPageLayout pour améliorer la cohérence
// tout en conservant tous les widgets spécialisés existants
return UnifiedPageLayout(
title: 'Tableau de bord',
icon: Icons.dashboard,
actions: [
IconButton(
icon: const Icon(Icons.animation),
onPressed: () {
Navigator.of(context).push(
PageTransitions.morphWithBlur(const AnimationsDemoPage()),
);
},
tooltip: 'Démonstration des animations',
),
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {
// TODO: Implémenter la navigation vers les notifications
},
),
IconButton(
icon: const Icon(Icons.bug_report),
onPressed: () {
Navigator.of(context).push(
PageTransitions.slideFromRight(const DebugApiTestPage()),
);
},
tooltip: 'Debug API',
),
IconButton(
icon: const Icon(Icons.speed),
onPressed: () {
Navigator.of(context).push(
PageTransitions.slideFromRight(const PerformanceDemoPage()),
);
},
tooltip: 'Performance',
),
IconButton(
icon: const Icon(Icons.settings_outlined),
onPressed: () {
// TODO: Implémenter la navigation vers les paramètres
},
),
],
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. ACCUEIL & CONTEXTE - Message de bienvenue personnalisé
// CONSERVÉ: Widget spécialisé avec toutes ses fonctionnalités
const WelcomeSectionWidget(),
const SizedBox(height: 24),
// 2. VISION GLOBALE - Indicateurs clés de performance (KPI)
// CONSERVÉ: KPI enrichis avec détails, cibles, périodes
const KPICardsWidget(),
const SizedBox(height: 24),
// 3. ACTIONS PRIORITAIRES - Actions rapides et gestion
// CONSERVÉ: Grille d'actions organisées par catégories
const QuickActionsWidget(),
const SizedBox(height: 24),
// 4. SUIVI TEMPS RÉEL - Flux d'activités en direct
// CONSERVÉ: Activités avec indicateur "Live" et horodatage
const RecentActivitiesWidget(),
const SizedBox(height: 24),
// 5. ANALYSES APPROFONDIES - Graphiques et tendances
// CONSERVÉ: 1617 lignes de graphiques sophistiqués avec fl_chart
const ChartsAnalyticsWidget(),
return Scaffold(
appBar: AppBar(
title: const Text('UnionFlow - Tableau de bord'),
backgroundColor: AppTheme.primaryColor,
foregroundColor: Colors.white,
elevation: 0,
actions: [
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Notifications - Fonctionnalité à venir'),
duration: Duration(seconds: 2),
),
);
},
),
IconButton(
icon: const Icon(Icons.settings_outlined),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Paramètres - Fonctionnalité à venir'),
duration: Duration(seconds: 2),
),
);
},
),
],
),
body: RefreshIndicator(
onRefresh: _refreshDashboard,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Message de bienvenue
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bienvenue sur UnionFlow',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
const SizedBox(height: 8),
Text(
'Votre plateforme de gestion d\'union familiale',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
),
],
),
),
),
const SizedBox(height: 24),
// Statistiques rapides
Row(
children: [
Expanded(
child: _buildStatCard(
'Membres',
'25',
Icons.people,
Colors.blue,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildStatCard(
'Cotisations',
'15',
Icons.payment,
Colors.green,
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildStatCard(
'Événements',
'8',
Icons.event,
Colors.orange,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildStatCard(
'Solidarité',
'3',
Icons.favorite,
Colors.red,
),
),
],
),
const SizedBox(height: 24),
// Actions rapides
Text(
'Actions rapides',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.5,
children: [
_buildActionCard(
'Nouveau membre',
Icons.person_add,
Colors.blue,
() => _showComingSoon('Nouveau membre'),
),
_buildActionCard(
'Nouvelle cotisation',
Icons.add_card,
Colors.green,
() => _showComingSoon('Nouvelle cotisation'),
),
_buildActionCard(
'Nouvel événement',
Icons.event_available,
Colors.orange,
() => _showComingSoon('Nouvel événement'),
),
_buildActionCard(
'Demande d\'aide',
Icons.help_outline,
Colors.red,
() => _showComingSoon('Demande d\'aide'),
),
],
),
const SizedBox(height: 24),
],
),
),
),
);
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(icon, color: color, size: 24),
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
Widget _buildActionCard(String title, IconData icon, Color color, VoidCallback onTap) {
return Card(
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 32),
const SizedBox(height: 8),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
}
void _showComingSoon(String feature) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$feature - Fonctionnalité à venir'),
duration: const Duration(seconds: 2),
),
);
}
Future<void> _refreshDashboard() async {
// Simuler un délai de rafraîchissement
await Future.delayed(const Duration(seconds: 1));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Tableau de bord actualisé'),
duration: Duration(seconds: 2),
backgroundColor: Colors.green,
),
);
}
}
}

View File

@@ -0,0 +1,178 @@
/// Dashboard Page Stable - Redirecteur vers Dashboard Adaptatif
/// Redirige automatiquement vers le nouveau système de dashboard adaptatif
library dashboard_page_stable;
import 'package:flutter/material.dart';
import 'adaptive_dashboard_page.dart';
/// Page Dashboard Stable - Maintenant un redirecteur
///
/// Cette page redirige automatiquement vers le nouveau système
/// de dashboard adaptatif basé sur les rôles utilisateurs.
class DashboardPageStable extends StatefulWidget {
const DashboardPageStable({super.key});
@override
State<DashboardPageStable> createState() => _DashboardPageStableState();
}
class _DashboardPageStableState extends State<DashboardPageStable> {
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
appBar: AppBar(
title: Text(
'UnionFlow Dashboard',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
color: ColorTokens.onSurface,
),
),
backgroundColor: ColorTokens.surface,
elevation: 0,
actions: [
IconButton(
onPressed: () => _showNotifications(),
icon: const Icon(Icons.notifications_outlined),
tooltip: 'Notifications',
),
IconButton(
onPressed: () => _showSettings(),
icon: const Icon(Icons.settings_outlined),
tooltip: 'Paramètres',
),
],
),
drawer: DashboardDrawer(
onNavigate: _onNavigate,
onLogout: _onLogout,
),
body: RefreshIndicator(
key: _refreshKey,
onRefresh: _refreshData,
child: SingleChildScrollView(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Message de bienvenue
DashboardWelcomeSection(
title: 'Bienvenue sur UnionFlow',
subtitle: 'Votre plateforme de gestion d\'union familiale',
),
const SizedBox(height: SpacingTokens.xl),
// Statistiques
DashboardStatsGrid(
onStatTap: _onStatTap,
),
const SizedBox(height: SpacingTokens.xl),
// Actions rapides
DashboardQuickActionsGrid(
onActionTap: _onActionTap,
),
const SizedBox(height: SpacingTokens.xl),
// Activité récente
DashboardRecentActivitySection(
onActivityTap: _onActivityTap,
),
const SizedBox(height: SpacingTokens.xl),
// Insights
DashboardInsightsSection(
onMetricTap: _onMetricTap,
),
],
),
),
),
);
}
// === CALLBACKS POUR LES WIDGETS MODULAIRES ===
/// Callback pour les actions sur les statistiques
void _onStatTap(String statType) {
debugPrint('Statistique tapée: $statType');
// TODO: Implémenter la navigation vers les détails
}
/// Callback pour les actions rapides
void _onActionTap(String actionType) {
debugPrint('Action rapide: $actionType');
// TODO: Implémenter les actions spécifiques
}
/// Callback pour les activités récentes
void _onActivityTap(String activityId) {
debugPrint('Activité tapée: $activityId');
// TODO: Implémenter la navigation vers les détails
}
/// Callback pour les métriques d'insights
void _onMetricTap(String metricType) {
debugPrint('Métrique tapée: $metricType');
// TODO: Implémenter la navigation vers les rapports
}
/// Callback pour la navigation du drawer
void _onNavigate(String route) {
Navigator.of(context).pop(); // Fermer le drawer
debugPrint('Navigation vers: $route');
// TODO: Implémenter la navigation
}
/// Callback pour la déconnexion
void _onLogout() {
Navigator.of(context).pop(); // Fermer le drawer
debugPrint('Déconnexion demandée');
// TODO: Implémenter la déconnexion
}
// === MÉTHODES UTILITAIRES ===
/// Actualise les données du dashboard
Future<void> _refreshData() async {
setState(() {
_isLoading = true;
});
// Simulation d'un appel API
await Future.delayed(const Duration(seconds: 1));
setState(() {
_isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Données actualisées'),
duration: Duration(seconds: 2),
),
);
}
}
/// Affiche les notifications
void _showNotifications() {
debugPrint('Afficher les notifications');
// TODO: Implémenter l'affichage des notifications
}
/// Affiche les paramètres
void _showSettings() {
debugPrint('Afficher les paramètres');
// TODO: Implémenter l'affichage des paramètres
}
}

View File

@@ -0,0 +1,121 @@
/// Dashboard Page Stable - Redirecteur vers Dashboard Adaptatif
/// Redirige automatiquement vers le nouveau système de dashboard adaptatif
library dashboard_page_stable;
import 'package:flutter/material.dart';
import 'adaptive_dashboard_page.dart';
/// Page Dashboard Stable - Maintenant un redirecteur
///
/// Cette page redirige automatiquement vers le nouveau système
/// de dashboard adaptatif basé sur les rôles utilisateurs.
class DashboardPageStable extends StatefulWidget {
const DashboardPageStable({super.key});
@override
State<DashboardPageStable> createState() => _DashboardPageStableState();
}
class _DashboardPageStableState extends State<DashboardPageStable> {
@override
void initState() {
super.initState();
// Rediriger automatiquement vers le dashboard adaptatif
WidgetsBinding.instance.addPostFrameCallback((_) {
_redirectToAdaptiveDashboard();
});
}
/// Redirige vers le dashboard adaptatif
void _redirectToAdaptiveDashboard() {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const AdaptiveDashboardPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 0.1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOutCubic,
)),
child: child,
),
);
},
transitionDuration: const Duration(milliseconds: 600),
),
);
}
@override
Widget build(BuildContext context) {
// Afficher un écran de chargement pendant la redirection
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFF6C5CE7),
Color(0xFF5A4FCF),
],
),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
Icon(
Icons.dashboard,
color: Colors.white,
size: 80,
),
SizedBox(height: 24),
// Titre
Text(
'UnionFlow',
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
// Indicateur de chargement
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
strokeWidth: 3,
),
),
SizedBox(height: 16),
// Message
Text(
'Chargement de votre dashboard...',
style: TextStyle(
color: Colors.white70,
fontSize: 16,
),
),
],
),
),
),
);
}
}

View File

@@ -1,439 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../shared/widgets/unified_components.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../core/animations/page_transitions.dart';
import '../../../demo/presentation/pages/animations_demo_page.dart';
import '../../../debug/debug_api_test_page.dart';
/// Page principale du tableau de bord UnionFlow - Version Unifiée
///
/// Utilise l'architecture unifiée avec composants standardisés pour :
/// - Cohérence visuelle parfaite avec les autres onglets
/// - Maintenabilité optimale et réutilisabilité maximale
/// - Performance 60 FPS avec animations fluides
/// - Expérience utilisateur homogène
class DashboardPageUnified extends StatelessWidget {
const DashboardPageUnified({super.key});
@override
Widget build(BuildContext context) {
return UnifiedPageLayout(
title: 'Tableau de bord',
subtitle: 'Vue d\'ensemble de votre association',
icon: Icons.dashboard,
iconColor: AppTheme.primaryColor,
actions: _buildActions(context),
body: Column(
children: [
_buildWelcomeSection(),
const SizedBox(height: AppTheme.spacingLarge),
_buildKPISection(),
const SizedBox(height: AppTheme.spacingLarge),
_buildQuickActionsSection(),
const SizedBox(height: AppTheme.spacingLarge),
_buildRecentActivitiesSection(),
const SizedBox(height: AppTheme.spacingLarge),
_buildAnalyticsSection(),
],
),
);
}
/// Actions de la barre d'outils
List<Widget> _buildActions(BuildContext context) {
return [
IconButton(
icon: const Icon(Icons.animation),
onPressed: () => Navigator.of(context).push(
PageTransitions.morphWithBlur(const AnimationsDemoPage()),
),
tooltip: 'Démonstration des animations',
),
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {
// TODO: Implémenter la navigation vers les notifications
},
tooltip: 'Notifications',
),
IconButton(
icon: const Icon(Icons.bug_report),
onPressed: () => Navigator.of(context).push(
PageTransitions.slideFromRight(const DebugApiTestPage()),
),
tooltip: 'Debug API',
),
IconButton(
icon: const Icon(Icons.settings_outlined),
onPressed: () {
// TODO: Implémenter la navigation vers les paramètres
},
tooltip: 'Paramètres',
),
];
}
/// Section d'accueil personnalisée
Widget _buildWelcomeSection() {
return UnifiedCard.elevated(
child: Padding(
padding: const EdgeInsets.all(AppTheme.spacingLarge),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(AppTheme.spacingMedium),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
),
child: Icon(
Icons.waving_hand,
color: AppTheme.primaryColor,
size: 32,
),
),
const SizedBox(width: AppTheme.spacingMedium),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bonjour !',
style: AppTheme.headlineSmall.copyWith(
color: AppTheme.primaryColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: AppTheme.spacingXSmall),
Text(
'Bienvenue sur votre tableau de bord UnionFlow',
style: AppTheme.bodyMedium.copyWith(
color: AppTheme.textSecondary,
),
),
],
),
),
],
),
),
);
}
/// Section des indicateurs clés de performance
Widget _buildKPISection() {
final kpis = [
UnifiedKPIData(
title: 'Membres',
value: '247',
icon: Icons.people,
color: AppTheme.primaryColor,
trend: UnifiedKPITrend(
direction: UnifiedKPITrendDirection.up,
value: '+12',
label: 'ce mois',
),
),
UnifiedKPIData(
title: 'Événements',
value: '18',
icon: Icons.event,
color: AppTheme.accentColor,
trend: UnifiedKPITrend(
direction: UnifiedKPITrendDirection.up,
value: '+3',
label: 'ce mois',
),
),
UnifiedKPIData(
title: 'Cotisations',
value: '89%',
icon: Icons.account_balance_wallet,
color: AppTheme.successColor,
trend: UnifiedKPITrend(
direction: UnifiedKPITrendDirection.up,
value: '+5%',
label: 'vs mois dernier',
),
),
UnifiedKPIData(
title: 'Trésorerie',
value: '12.5K€',
icon: Icons.euro,
color: AppTheme.warningColor,
trend: UnifiedKPITrend(
direction: UnifiedKPITrendDirection.stable,
value: '0%',
label: 'stable',
),
),
];
return UnifiedKPISection(
title: 'Vue d\'ensemble',
kpis: kpis,
);
}
/// Section des actions rapides
Widget _buildQuickActionsSection() {
final actions = [
UnifiedQuickAction(
id: 'add_member',
title: 'Nouveau\nMembre',
icon: Icons.person_add,
color: AppTheme.primaryColor,
),
UnifiedQuickAction(
id: 'add_event',
title: 'Nouvel\nÉvénement',
icon: Icons.event_available,
color: AppTheme.accentColor,
badgeCount: 3,
),
UnifiedQuickAction(
id: 'manage_cotisations',
title: 'Gérer\nCotisations',
icon: Icons.account_balance_wallet,
color: AppTheme.successColor,
badgeCount: 7,
),
UnifiedQuickAction(
id: 'reports',
title: 'Rapports\n& Stats',
icon: Icons.analytics,
color: AppTheme.infoColor,
),
UnifiedQuickAction(
id: 'communications',
title: 'Envoyer\nMessage',
icon: Icons.send,
color: AppTheme.warningColor,
),
UnifiedQuickAction(
id: 'settings',
title: 'Paramètres\nAssociation',
icon: Icons.settings,
color: AppTheme.textSecondary,
),
];
return UnifiedQuickActionsSection(
title: 'Actions rapides',
actions: actions,
onActionTap: _handleQuickAction,
);
}
/// Section des activités récentes
Widget _buildRecentActivitiesSection() {
final activities = [
_ActivityItem(
title: 'Nouveau membre inscrit',
subtitle: 'Marie Dubois a rejoint l\'association',
icon: Icons.person_add,
color: AppTheme.successColor,
time: 'Il y a 2h',
),
_ActivityItem(
title: 'Événement créé',
subtitle: 'Assemblée Générale 2024 programmée',
icon: Icons.event,
color: AppTheme.accentColor,
time: 'Il y a 4h',
),
_ActivityItem(
title: 'Cotisation reçue',
subtitle: 'Jean Martin - Cotisation annuelle',
icon: Icons.payment,
color: AppTheme.primaryColor,
time: 'Il y a 6h',
),
];
return UnifiedCard.elevated(
child: Padding(
padding: const EdgeInsets.all(AppTheme.spacingLarge),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.timeline,
color: AppTheme.primaryColor,
size: 24,
),
const SizedBox(width: AppTheme.spacingSmall),
Text(
'Activités récentes',
style: AppTheme.titleMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: AppTheme.spacingMedium),
...activities.map((activity) => _buildActivityItem(activity)),
],
),
),
);
}
/// Section d'analyses et graphiques
Widget _buildAnalyticsSection() {
return UnifiedCard.elevated(
child: Padding(
padding: const EdgeInsets.all(AppTheme.spacingLarge),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.analytics,
color: AppTheme.accentColor,
size: 24,
),
const SizedBox(width: AppTheme.spacingSmall),
Text(
'Analyses & Tendances',
style: AppTheme.titleMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const Spacer(),
UnifiedButton.tertiary(
text: 'Voir plus',
size: UnifiedButtonSize.small,
onPressed: () {
// TODO: Navigation vers analyses détaillées
},
),
],
),
const SizedBox(height: AppTheme.spacingMedium),
Container(
height: 120,
decoration: BoxDecoration(
color: AppTheme.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bar_chart,
color: AppTheme.accentColor,
size: 48,
),
const SizedBox(height: AppTheme.spacingSmall),
Text(
'Graphiques interactifs',
style: AppTheme.bodyMedium.copyWith(
color: AppTheme.textSecondary,
),
),
],
),
),
),
],
),
),
);
}
/// Construit un élément d'activité
Widget _buildActivityItem(_ActivityItem activity) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppTheme.spacingSmall),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(AppTheme.spacingSmall),
decoration: BoxDecoration(
color: activity.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppTheme.borderRadiusSmall),
),
child: Icon(
activity.icon,
color: activity.color,
size: 16,
),
),
const SizedBox(width: AppTheme.spacingMedium),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: AppTheme.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
activity.subtitle,
style: AppTheme.bodySmall.copyWith(
color: AppTheme.textSecondary,
),
),
],
),
),
Text(
activity.time,
style: AppTheme.bodySmall.copyWith(
color: AppTheme.textSecondary,
),
),
],
),
);
}
/// Gère les actions rapides
void _handleQuickAction(UnifiedQuickAction action) {
// TODO: Implémenter la navigation selon l'action
switch (action.id) {
case 'add_member':
// Navigation vers ajout membre
break;
case 'add_event':
// Navigation vers ajout événement
break;
case 'manage_cotisations':
// Navigation vers gestion cotisations
break;
case 'reports':
// Navigation vers rapports
break;
case 'communications':
// Navigation vers communications
break;
case 'settings':
// Navigation vers paramètres
break;
}
}
}
/// Modèle pour les éléments d'activité
class _ActivityItem {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final String time;
const _ActivityItem({
required this.title,
required this.subtitle,
required this.icon,
required this.color,
required this.time,
});
}

View File

@@ -0,0 +1,322 @@
/// Dashboard Membre Actif - Activity Center Personnalisé
/// Interface personnalisée pour participation active
library active_member_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Activity Center pour Membre Actif
class ActiveMemberDashboard extends StatelessWidget {
const ActiveMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Membre Actif
SliverAppBar(
expandedHeight: 160,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF00B894), // Vert communauté
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Activity Center',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF00B894), Color(0xFF00A085)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Center(
child: Icon(Icons.groups, color: Colors.white, size: 60),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Bienvenue personnalisé
_buildPersonalizedWelcome(),
const SizedBox(height: SpacingTokens.xl),
// Mes statistiques
_buildMyStats(),
const SizedBox(height: SpacingTokens.xl),
// Actions membres
_buildMemberActions(),
const SizedBox(height: SpacingTokens.xl),
// Événements à venir
_buildUpcomingEvents(),
const SizedBox(height: SpacingTokens.xl),
// Mon activité
_buildMyActivity(),
],
),
),
),
],
),
);
}
Widget _buildPersonalizedWelcome() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF00B894), Color(0xFF00CEC9)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
),
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundColor: Colors.white,
child: Icon(Icons.person, color: Color(0xFF00B894), size: 30),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bonjour, Marie !',
style: TypographyTokens.headlineMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(
'Membre depuis 2 ans • Niveau Actif',
style: TypographyTokens.bodyMedium.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
],
),
);
}
Widget _buildMyStats() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mes Statistiques',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardStatsGrid(
stats: [
DashboardStat(
icon: Icons.event_available,
value: '12',
title: 'Événements',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardStat(
icon: Icons.volunteer_activism,
value: '3',
title: 'Solidarité',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardStat(
icon: Icons.payment,
value: 'À jour',
title: 'Cotisations',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardStat(
icon: Icons.star,
value: '4.8',
title: 'Engagement',
color: const Color(0xFFE17055),
onTap: () {},
),
],
onStatTap: (type) {},
),
],
);
}
Widget _buildMemberActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions Rapides',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
actions: [
DashboardQuickAction(
icon: Icons.event,
title: 'Créer Événement',
subtitle: 'Organiser activité',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.volunteer_activism,
title: 'Demande Aide',
subtitle: 'Solidarité',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.account_circle,
title: 'Mon Profil',
subtitle: 'Modifier infos',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.message,
title: 'Contacter',
subtitle: 'Support',
color: const Color(0xFFE17055),
onTap: () {},
),
],
onActionTap: (type) {},
),
],
);
}
Widget _buildUpcomingEvents() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Événements à Venir',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const Spacer(),
TextButton(
onPressed: () {},
child: const Text('Voir tout'),
),
],
),
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)),
],
),
),
title: const Text('Assemblée Générale'),
subtitle: const Text('Salle communale • 19h00'),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
),
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 Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('22', style: TextStyle(fontWeight: FontWeight.bold)),
Text('DÉC', style: TextStyle(fontSize: 10)),
],
),
),
title: const Text('Soirée de Noël'),
subtitle: const Text('Restaurant Le Gourmet • 20h00'),
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
),
],
),
),
],
);
}
Widget _buildMyActivity() {
return DashboardRecentActivitySection(
activities: [
DashboardActivity(
title: 'Participation confirmée',
subtitle: 'Assemblée Générale',
icon: Icons.check_circle,
color: const Color(0xFF00B894),
time: 'Il y a 2h',
),
DashboardActivity(
title: 'Cotisation payée',
subtitle: 'Décembre 2024',
icon: Icons.payment,
color: const Color(0xFF0984E3),
time: 'Il y a 1j',
),
DashboardActivity(
title: 'Événement créé',
subtitle: 'Sortie ski de fond',
icon: Icons.event,
color: const Color(0xFF00CEC9),
time: 'Il y a 3j',
),
],
onActivityTap: (id) {},
);
}
}

View File

@@ -0,0 +1,236 @@
/// Dashboard Modérateur - Management Hub Focalisé
/// Outils de modération et gestion partielle
library moderator_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Management Hub pour Modérateur
class ModeratorDashboard extends StatelessWidget {
const ModeratorDashboard({super.key});
@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,
),
),
child: const Center(
child: Icon(Icons.manage_accounts, color: Colors.white, size: 60),
),
),
),
),
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(),
],
),
),
),
],
),
);
}
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: [
DashboardStat(
icon: Icons.flag,
value: '12',
title: 'Signalements',
color: const Color(0xFFE17055),
onTap: () {},
),
DashboardStat(
icon: Icons.pending_actions,
value: '8',
title: 'En Attente',
color: const Color(0xFFD63031),
onTap: () {},
),
DashboardStat(
icon: Icons.check_circle,
value: '45',
title: 'Résolus',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardStat(
icon: Icons.people,
value: '156',
title: 'Membres',
color: const Color(0xFF0984E3),
onTap: () {},
),
],
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(
actions: [
DashboardQuickAction(
icon: Icons.gavel,
title: 'Modérer',
subtitle: 'Contenu signalé',
color: const Color(0xFFE17055),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.person_remove,
title: 'Suspendre',
subtitle: 'Membre problématique',
color: const Color(0xFFD63031),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.message,
title: 'Communiquer',
subtitle: 'Envoyer message',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.report,
title: 'Rapport',
subtitle: 'Activité modération',
color: const Color(0xFF6C5CE7),
onTap: () {},
),
],
onActionTap: (type) {},
),
],
);
}
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: Column(
children: [
ListTile(
leading: const CircleAvatar(
backgroundColor: Color(0xFFFFE0E0),
child: Icon(Icons.flag, color: Color(0xFFD63031)),
),
title: const Text('Contenu inapproprié signalé'),
subtitle: const Text('Commentaire sur événement'),
trailing: const Text('Urgent'),
),
const Divider(height: 1),
ListTile(
leading: const CircleAvatar(
backgroundColor: Color(0xFFFFF3E0),
child: Icon(Icons.person_add, color: Color(0xFFE17055)),
),
title: const Text('Demande d\'adhésion'),
subtitle: const Text('Marie Dubois'),
trailing: const Text('2j'),
),
],
),
),
],
);
}
Widget _buildRecentActivity() {
return DashboardRecentActivitySection(
activities: [
DashboardActivity(
title: 'Signalement traité',
subtitle: 'Contenu supprimé',
icon: Icons.check_circle,
color: const Color(0xFF00B894),
time: 'Il y a 1h',
),
DashboardActivity(
title: 'Membre suspendu',
subtitle: 'Violation des règles',
icon: Icons.person_remove,
color: const Color(0xFFD63031),
time: 'Il y a 3h',
),
],
onActivityTap: (id) {},
);
}
}

View File

@@ -0,0 +1,558 @@
/// Dashboard Administrateur d'Organisation - Control Panel Sophistiqué
/// Gestion complète de l'organisation avec outils avancés
library org_admin_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Control Panel pour Administrateur d'Organisation
///
/// Fonctionnalités exclusives :
/// - Gestion complète des membres
/// - Contrôle financier avancé
/// - Configuration organisation
/// - Rapports et analytics
/// - Outils de communication
class OrgAdminDashboard extends StatefulWidget {
const OrgAdminDashboard({super.key});
@override
State<OrgAdminDashboard> createState() => _OrgAdminDashboardState();
}
class _OrgAdminDashboardState extends State<OrgAdminDashboard> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar avec gradient Org Admin
SliverAppBar(
expandedHeight: 180,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF0984E3), // Bleu corporate
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Control Panel',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0984E3), // Bleu principal
Color(0xFF0770C4), // Bleu plus foncé
Color(0xFF055A9F), // Bleu profond
],
),
),
child: Stack(
children: [
// Motif corporate
Positioned.fill(
child: CustomPaint(
painter: _CorporatePatternPainter(),
),
),
// Contenu de l'en-tête
Positioned(
bottom: 60,
left: 20,
right: 20,
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: const Icon(
Icons.business_center,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Administrateur',
style: TypographyTokens.headlineSmall.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(
'Association des Développeurs',
style: TypographyTokens.bodyMedium.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
],
),
),
],
),
),
),
),
// Contenu principal
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Métriques organisation
_buildOrganizationMetricsSection(),
const SizedBox(height: SpacingTokens.xl),
// Actions rapides admin
_buildAdminQuickActionsSection(),
const SizedBox(height: SpacingTokens.xl),
// Gestion des membres
_buildMemberManagementSection(),
const SizedBox(height: SpacingTokens.xl),
// Finances et budget
_buildFinancialOverviewSection(),
const SizedBox(height: SpacingTokens.xl),
// Activité récente
_buildRecentActivitySection(),
],
),
),
),
],
),
);
}
/// Section métriques organisation
Widget _buildOrganizationMetricsSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Vue d\'ensemble Organisation',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
DashboardStatsGrid(
stats: [
DashboardStat(
icon: Icons.people,
value: '156',
title: 'Membres Actifs',
color: const Color(0xFF00B894),
onTap: () => _onStatTap('members'),
),
DashboardStat(
icon: Icons.euro,
value: '12,450€',
title: 'Budget Mensuel',
color: const Color(0xFF0984E3),
onTap: () => _onStatTap('budget'),
),
DashboardStat(
icon: Icons.event,
value: '8',
title: 'Événements',
color: const Color(0xFFE17055),
onTap: () => _onStatTap('events'),
),
DashboardStat(
icon: Icons.trending_up,
value: '94%',
title: 'Satisfaction',
color: const Color(0xFF00CEC9),
onTap: () => _onStatTap('satisfaction'),
),
],
onStatTap: _onStatTap,
),
],
);
}
/// Section actions rapides admin
Widget _buildAdminQuickActionsSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions Administrateur',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: SpacingTokens.md,
mainAxisSpacing: SpacingTokens.md,
childAspectRatio: 2.5,
children: [
_buildAdminActionCard(
'Approuver Membres',
'5 en attente',
Icons.person_add,
const Color(0xFF00B894),
() => _onAdminAction('approve_members'),
),
_buildAdminActionCard(
'Gérer Budget',
'Révision mensuelle',
Icons.account_balance_wallet,
const Color(0xFF0984E3),
() => _onAdminAction('manage_budget'),
),
_buildAdminActionCard(
'Configurer Org',
'Paramètres avancés',
Icons.settings,
const Color(0xFFE17055),
() => _onAdminAction('configure_org'),
),
_buildAdminActionCard(
'Rapports',
'Générer rapport',
Icons.assessment,
const Color(0xFF6C5CE7),
() => _onAdminAction('generate_reports'),
),
],
),
],
);
}
/// Carte d'action admin
Widget _buildAdminActionCard(
String title,
String subtitle,
IconData icon,
Color color,
VoidCallback onTap,
) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(RadiusTokens.md),
child: Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
border: Border.all(
color: color.withOpacity(0.2),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: SpacingTokens.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
subtitle,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
),
],
),
),
);
}
/// Section gestion des membres
Widget _buildMemberManagementSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Gestion des Membres',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const Spacer(),
TextButton.icon(
onPressed: () => _onViewAllMembers(),
icon: const Icon(Icons.arrow_forward),
label: const Text('Voir tout'),
),
],
),
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: [
_buildMemberItem(
'Marie Dubois',
'Demande d\'adhésion',
Icons.person_add,
Colors.orange,
'En attente',
),
const Divider(height: 1),
_buildMemberItem(
'Jean Martin',
'Cotisation en retard',
Icons.warning,
Colors.red,
'15 jours',
),
const Divider(height: 1),
_buildMemberItem(
'Sophie Laurent',
'Nouveau membre actif',
Icons.check_circle,
Colors.green,
'Aujourd\'hui',
),
],
),
),
],
);
}
/// Item de membre
Widget _buildMemberItem(
String name,
String status,
IconData icon,
Color color,
String time,
) {
return ListTile(
leading: CircleAvatar(
backgroundColor: color.withOpacity(0.1),
child: Icon(icon, color: color, size: 20),
),
title: Text(
name,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(status),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
time,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
const SizedBox(height: 2),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: ColorTokens.textSecondary,
),
],
),
onTap: () => _onMemberTap(name),
);
}
/// Section aperçu financier
Widget _buildFinancialOverviewSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Aperçu Financier',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
DashboardInsightsSection(
metrics: [
DashboardMetric(
label: 'Cotisations collectées',
value: '89%',
progress: 0.89,
color: const Color(0xFF00B894),
),
DashboardMetric(
label: 'Budget utilisé',
value: '67%',
progress: 0.67,
color: const Color(0xFF0984E3),
),
DashboardMetric(
label: 'Objectif annuel',
value: '78%',
progress: 0.78,
color: const Color(0xFFE17055),
),
],
),
],
);
}
/// Section activité récente
Widget _buildRecentActivitySection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Activité Récente',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
DashboardRecentActivitySection(
activities: [
DashboardActivity(
title: 'Nouveau membre approuvé',
subtitle: 'Sophie Laurent rejoint l\'organisation',
icon: Icons.person_add,
color: const Color(0xFF00B894),
time: 'Il y a 2h',
),
DashboardActivity(
title: 'Budget mis à jour',
subtitle: 'Allocation événements modifiée',
icon: Icons.account_balance_wallet,
color: const Color(0xFF0984E3),
time: 'Il y a 4h',
),
DashboardActivity(
title: 'Rapport généré',
subtitle: 'Rapport mensuel d\'activité',
icon: Icons.assessment,
color: const Color(0xFF6C5CE7),
time: 'Il y a 1j',
),
],
onActivityTap: (activityId) => _onActivityTap(activityId),
),
],
);
}
// === CALLBACKS ===
void _onStatTap(String statType) {
// Navigation vers les détails de la statistique
}
void _onAdminAction(String action) {
// Exécuter l'action admin
}
void _onViewAllMembers() {
// Navigation vers la liste complète des membres
}
void _onMemberTap(String memberName) {
// Navigation vers le profil du membre
}
void _onActivityTap(String activityId) {
// Navigation vers les détails de l'activité
}
}
/// Painter pour le motif corporate de l'en-tête
class _CorporatePatternPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.08)
..strokeWidth = 2
..style = PaintingStyle.stroke;
// Dessiner un motif corporate sophistiqué
for (int i = 0; i < 8; i++) {
final path = Path();
path.moveTo(i * size.width / 8, 0);
path.lineTo(i * size.width / 8 + size.width / 16, size.height);
canvas.drawPath(path, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,11 @@
/// 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 'active_member_dashboard.dart';
export 'simple_member_dashboard.dart';
export 'visitor_dashboard.dart';

View File

@@ -0,0 +1,371 @@
/// Dashboard Membre Simple - Personal Space Minimaliste
/// Interface simplifiée pour accès basique
library simple_member_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Personal Space pour Membre Simple
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,
),
),
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),
),
],
),
child: Row(
children: [
const CircleAvatar(
radius: 35,
backgroundColor: Color(0xFF00CEC9),
child: Icon(Icons.person, color: Colors.white, size: 35),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pierre Dupont',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
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,
),
),
),
],
),
),
],
),
);
}
Widget _buildMyInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mes Informations',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardStatsGrid(
stats: [
DashboardStat(
icon: Icons.payment,
value: 'À jour',
title: 'Cotisations',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardStat(
icon: Icons.event,
value: '2',
title: 'Événements',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardStat(
icon: Icons.account_circle,
value: '100%',
title: 'Profil',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardStat(
icon: Icons.notifications,
value: '3',
title: 'Notifications',
color: const Color(0xFFE17055),
onTap: () {},
),
],
onStatTap: (type) {},
),
],
);
}
Widget _buildSimpleActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions Disponibles',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
actions: [
DashboardQuickAction(
icon: Icons.edit,
title: 'Modifier Profil',
subtitle: 'Mes informations',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.payment,
title: 'Mes Cotisations',
subtitle: 'Historique paiements',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.event,
title: 'Événements',
subtitle: 'Voir les événements',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.help,
title: 'Aide',
subtitle: 'Support & FAQ',
color: const Color(0xFFE17055),
onTap: () {},
),
],
onActionTap: (type) {},
),
],
);
}
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),
DashboardRecentActivitySection(
activities: [
DashboardActivity(
title: 'Cotisation payée',
subtitle: 'Décembre 2024',
icon: Icons.payment,
color: const Color(0xFF00B894),
time: 'Il y a 1j',
),
DashboardActivity(
title: 'Profil mis à jour',
subtitle: 'Informations personnelles',
icon: Icons.edit,
color: const Color(0xFF00CEC9),
time: 'Il y a 1 sem',
),
DashboardActivity(
title: 'Inscription événement',
subtitle: 'Assemblée Générale',
icon: Icons.event,
color: const Color(0xFF0984E3),
time: 'Il y a 2 sem',
),
],
onActivityTap: (id) {},
),
],
);
}
}

View File

@@ -0,0 +1,514 @@
/// Dashboard Super Administrateur - Command Center Ultra-Sophistiqué
/// Vue globale multi-organisations avec métriques système avancées
library super_admin_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Command Center pour Super Administrateur
///
/// Fonctionnalités exclusives :
/// - Vue globale multi-organisations
/// - Métriques système en temps réel
/// - Outils d'administration avancés
/// - Monitoring et analytics
/// - Gestion des utilisateurs globale
class SuperAdminDashboard extends StatefulWidget {
const SuperAdminDashboard({super.key});
@override
State<SuperAdminDashboard> createState() => _SuperAdminDashboardState();
}
class _SuperAdminDashboardState extends State<SuperAdminDashboard>
with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar avec gradient Super Admin
SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF6C5CE7), // Violet Super Admin
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Command Center',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF6C5CE7), // Violet principal
Color(0xFF5A4FCF), // Violet plus foncé
Color(0xFF4834D4), // Violet profond
],
),
),
child: Stack(
children: [
// Motif géométrique sophistiqué
Positioned.fill(
child: CustomPaint(
painter: _GeometricPatternPainter(),
),
),
// Contenu de l'en-tête
Positioned(
bottom: 60,
left: 20,
right: 20,
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: const Icon(
Icons.admin_panel_settings,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Super Administrateur',
style: TypographyTokens.headlineSmall.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(
'Contrôle total du système',
style: TypographyTokens.bodyMedium.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
],
),
),
],
),
),
),
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.white,
labelColor: Colors.white,
unselectedLabelColor: Colors.white.withOpacity(0.7),
tabs: const [
Tab(text: 'Vue Globale'),
Tab(text: 'Organisations'),
Tab(text: 'Système'),
Tab(text: 'Analytics'),
],
),
),
// Contenu des onglets
SliverFillRemaining(
child: TabBarView(
controller: _tabController,
children: [
_buildGlobalOverviewTab(),
_buildOrganizationsTab(),
_buildSystemTab(),
_buildAnalyticsTab(),
],
),
),
],
),
);
}
/// Onglet Vue Globale
Widget _buildGlobalOverviewTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Métriques globales
_buildGlobalMetricsSection(),
const SizedBox(height: SpacingTokens.xl),
// Alertes système
_buildSystemAlertsSection(),
const SizedBox(height: SpacingTokens.xl),
// Activité récente globale
_buildGlobalActivitySection(),
],
),
);
}
/// Section métriques globales
Widget _buildGlobalMetricsSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Métriques Globales',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
// Grille de métriques système
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: SpacingTokens.md,
mainAxisSpacing: SpacingTokens.md,
childAspectRatio: 1.4,
children: [
_buildSystemMetricCard(
'Organisations',
'247',
'+12 ce mois',
Icons.business,
const Color(0xFF0984E3),
),
_buildSystemMetricCard(
'Utilisateurs',
'15,847',
'+1,234 ce mois',
Icons.people,
const Color(0xFF00B894),
),
_buildSystemMetricCard(
'Uptime',
'99.97%',
'30 derniers jours',
Icons.trending_up,
const Color(0xFF00CEC9),
),
_buildSystemMetricCard(
'Performance',
'1.2s',
'Temps de réponse',
Icons.speed,
const Color(0xFFE17055),
),
],
),
],
);
}
/// Carte de métrique système
Widget _buildSystemMetricCard(
String title,
String value,
String subtitle,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.md),
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(icon, color: color, size: 20),
),
const Spacer(),
Icon(
Icons.trending_up,
color: Colors.green,
size: 16,
),
],
),
const SizedBox(height: SpacingTokens.sm),
Text(
value,
style: TypographyTokens.headlineLarge.copyWith(
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
title,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
subtitle,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
);
}
/// Section alertes système
Widget _buildSystemAlertsSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Alertes Système',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: Text(
'3 critiques',
style: TypographyTokens.bodySmall.copyWith(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: SpacingTokens.md),
// Liste des alertes
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: [
_buildAlertItem(
'Charge CPU élevée',
'Serveur principal à 89%',
Icons.warning,
Colors.orange,
'2 min',
),
const Divider(height: 1),
_buildAlertItem(
'Espace disque faible',
'Base de données à 92%',
Icons.error,
Colors.red,
'5 min',
),
const Divider(height: 1),
_buildAlertItem(
'Connexions simultanées',
'Pic de trafic détecté',
Icons.info,
Colors.blue,
'12 min',
),
],
),
),
],
);
}
/// Item d'alerte
Widget _buildAlertItem(
String title,
String description,
IconData icon,
Color color,
String time,
) {
return ListTile(
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(icon, color: color, size: 20),
),
title: Text(
title,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(description),
trailing: Text(
time,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
);
}
/// Section activité globale
Widget _buildGlobalActivitySection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Activité Récente Globale',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.md),
DashboardRecentActivitySection(
activities: [
DashboardActivity(
title: 'Nouvelle organisation créée',
subtitle: 'Association des Développeurs',
icon: Icons.business,
color: const Color(0xFF0984E3),
time: 'Il y a 5 min',
),
DashboardActivity(
title: 'Mise à jour système',
subtitle: 'Version 2.1.4 déployée',
icon: Icons.system_update,
color: const Color(0xFF00B894),
time: 'Il y a 15 min',
),
DashboardActivity(
title: 'Alerte sécurité résolue',
subtitle: 'Tentative d\'intrusion bloquée',
icon: Icons.security,
color: const Color(0xFFE17055),
time: 'Il y a 32 min',
),
],
onActivityTap: (activityId) {
// Navigation vers les détails
},
),
],
);
}
/// Onglet Organisations (placeholder)
Widget _buildOrganizationsTab() {
return const Center(
child: Text('Gestion des Organisations'),
);
}
/// Onglet Système (placeholder)
Widget _buildSystemTab() {
return const Center(
child: Text('Administration Système'),
);
}
/// Onglet Analytics (placeholder)
Widget _buildAnalyticsTab() {
return const Center(
child: Text('Analytics Avancées'),
);
}
}
/// Painter pour le motif géométrique de l'en-tête
class _GeometricPatternPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.1)
..strokeWidth = 1
..style = PaintingStyle.stroke;
// Dessiner un motif géométrique sophistiqué
for (int i = 0; i < 10; i++) {
final rect = Rect.fromLTWH(
i * size.width / 10,
i * size.height / 10,
size.width / 5,
size.height / 5,
);
canvas.drawRect(rect, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,550 @@
/// Dashboard Visiteur - Landing Experience Accueillante
/// Interface publique pour découvrir l'organisation
library visitor_dashboard;
import 'package:flutter/material.dart';
import '../../../../../core/design_system/tokens/tokens.dart';
import '../../widgets/widgets.dart';
/// Dashboard Landing Experience pour Visiteur
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),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Stack(
children: [
// Motif d'accueil
Positioned.fill(
child: CustomPaint(
painter: _WelcomePatternPainter(),
),
),
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,
),
),
],
),
),
],
),
),
),
),
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(),
],
),
),
),
],
),
);
}
Widget _buildWelcomeMessage() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.info_outline, color: Colors.white, size: 30),
const SizedBox(width: SpacingTokens.sm),
Text(
'Découvrez notre communauté',
style: TypographyTokens.headlineMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: SpacingTokens.md),
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),
),
),
],
),
);
}
Widget _buildAboutOrganization() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'À Propos de Nous',
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: [
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),
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)),
],
),
),
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 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: Column(
children: [
ListTile(
leading: const Icon(Icons.email, color: Color(0xFF6C5CE7)),
title: const Text('Email'),
subtitle: const Text('contact@association-dev.fr'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: const Icon(Icons.phone, color: Color(0xFF6C5CE7)),
title: const Text('Téléphone'),
subtitle: const Text('+33 1 23 45 67 89'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: const Icon(Icons.location_on, color: Color(0xFF6C5CE7)),
title: const Text('Adresse'),
subtitle: const 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;
}

View File

@@ -1,106 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
/// Widget de carte d'action rapide réutilisable
///
/// Affiche une action cliquable avec:
/// - Icône colorée dans un conteneur arrondi
/// - Titre principal
/// - Sous-titre descriptif
/// - Interaction tactile avec feedback visuel
/// - Callback personnalisable pour l'action
class ActionCardWidget extends StatelessWidget {
/// Titre de l'action
final String title;
/// Description de l'action
final String subtitle;
/// Icône représentative
final IconData icon;
/// Couleur thématique de l'action
final Color color;
/// Callback exécuté lors du tap
final VoidCallback? onTap;
const ActionCardWidget({
super.key,
required this.title,
required this.subtitle,
required this.icon,
required this.color,
this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap ?? () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$title - En cours de développement'),
backgroundColor: color,
),
);
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: color.withOpacity(0.2)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 8,
offset: const Offset(0, 1),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 18,
),
),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
subtitle,
style: const TextStyle(
fontSize: 10,
color: AppTheme.textSecondary,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
}

View File

@@ -1,165 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
import 'action_card_widget.dart';
/// Widget de section des actions rapides et de gestion
///
/// Affiche une grille d'actions rapides organisées par catégories:
/// - Actions principales (nouveau membre, créer événement)
/// - Gestion financière (encaisser cotisation, relances)
/// - Communication (messages, convocations)
/// - Rapports et conformité (OHADA, exports)
/// - Urgences et support (alertes, assistance)
class QuickActionsWidget extends StatelessWidget {
const QuickActionsWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions rapides & Gestion',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 12),
// Grille compacte 3x4 - Actions organisées par priorité
// Première ligne - Actions principales (3 colonnes)
Row(
children: [
Expanded(
child: ActionCardWidget(
title: 'Nouveau membre',
subtitle: 'Inscription',
icon: Icons.person_add,
color: AppTheme.primaryColor,
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Créer événement',
subtitle: 'Organiser',
icon: Icons.event_available,
color: AppTheme.secondaryColor,
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Encaisser',
subtitle: 'Cotisation',
icon: Icons.payment,
color: AppTheme.successColor,
),
),
],
),
const SizedBox(height: 8),
// Deuxième ligne - Gestion et communication
Row(
children: [
Expanded(
child: ActionCardWidget(
title: 'Relances',
subtitle: 'SMS/Email',
icon: Icons.notifications_active,
color: AppTheme.warningColor,
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Message groupe',
subtitle: 'WhatsApp',
icon: Icons.message,
color: const Color(0xFF25D366),
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Convoquer AG',
subtitle: 'Assemblée',
icon: Icons.groups,
color: const Color(0xFF9C27B0),
),
),
],
),
const SizedBox(height: 8),
// Troisième ligne - Rapports et conformité
Row(
children: [
Expanded(
child: ActionCardWidget(
title: 'Rapport OHADA',
subtitle: 'Conformité',
icon: Icons.gavel,
color: const Color(0xFF795548),
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Export données',
subtitle: 'Excel/PDF',
icon: Icons.file_download,
color: AppTheme.infoColor,
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Statistiques',
subtitle: 'Analyses',
icon: Icons.analytics,
color: const Color(0xFF00BCD4),
),
),
],
),
const SizedBox(height: 8),
// Quatrième ligne - Support et urgences
Row(
children: [
Expanded(
child: ActionCardWidget(
title: 'Alerte urgente',
subtitle: 'Critique',
icon: Icons.emergency,
color: AppTheme.errorColor,
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Support tech',
subtitle: 'Assistance',
icon: Icons.support_agent,
color: const Color(0xFF607D8B),
),
),
const SizedBox(width: 8),
Expanded(
child: ActionCardWidget(
title: 'Paramètres',
subtitle: 'Configuration',
icon: Icons.settings,
color: const Color(0xFF9E9E9E),
),
),
],
),
],
);
}
}

View File

@@ -1,148 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
/// Widget d'élément d'activité récente réutilisable
///
/// Affiche une activité avec:
/// - Icône colorée avec indicateur "nouveau" optionnel
/// - Titre et description
/// - Horodatage avec mise en évidence pour les nouveaux éléments
/// - Badge "NOUVEAU" pour les activités récentes
/// - Indicateur visuel pour les nouvelles activités
class ActivityItemWidget extends StatelessWidget {
/// Titre de l'activité
final String title;
/// Description détaillée de l'activité
final String description;
/// Icône représentative
final IconData icon;
/// Couleur thématique
final Color color;
/// Horodatage de l'activité
final String time;
/// Indique si l'activité est nouvelle
final bool isNew;
const ActivityItemWidget({
super.key,
required this.title,
required this.description,
required this.icon,
required this.color,
required this.time,
this.isNew = false,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Stack(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 16,
),
),
if (isNew)
Positioned(
top: -2,
right: -2,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: AppTheme.errorColor,
shape: BoxShape.circle,
),
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: isNew ? FontWeight.w700 : FontWeight.w600,
color: isNew ? AppTheme.textPrimary : AppTheme.textPrimary,
),
),
),
if (isNew)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppTheme.errorColor,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'NOUVEAU',
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
const SizedBox(height: 2),
Text(
description,
style: TextStyle(
fontSize: 12,
color: isNew ? AppTheme.textPrimary : AppTheme.textSecondary,
fontWeight: isNew ? FontWeight.w500 : FontWeight.normal,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
time,
style: TextStyle(
fontSize: 12,
color: isNew ? AppTheme.primaryColor : AppTheme.textHint,
fontWeight: isNew ? FontWeight.w600 : FontWeight.normal,
),
),
if (isNew)
const SizedBox(height: 2),
if (isNew)
const Icon(
Icons.fiber_new,
size: 12,
color: AppTheme.errorColor,
),
],
),
],
),
);
}
}

View File

@@ -1,162 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
import 'activity_item_widget.dart';
/// Widget de section des activités récentes en temps réel
///
/// Affiche un flux d'activités en temps réel avec:
/// - En-tête avec indicateur "Live" et bouton "Tout voir"
/// - Liste d'activités avec indicateurs visuels pour les nouveaux éléments
/// - Séparateurs entre les éléments
/// - Horodatage précis pour chaque activité
/// - Icônes et couleurs thématiques par type d'activité
class RecentActivitiesWidget extends StatelessWidget {
const RecentActivitiesWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Flux d\'activités en temps réel',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppTheme.successColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 4,
height: 4,
decoration: const BoxDecoration(
color: AppTheme.successColor,
shape: BoxShape.circle,
),
),
const SizedBox(width: 3),
const Text(
'Live',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: AppTheme.successColor,
),
),
],
),
),
const SizedBox(width: 6),
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: const Text(
'Tout',
style: TextStyle(fontSize: 12),
),
),
],
),
const SizedBox(height: 16),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Column(
children: [
ActivityItemWidget(
title: 'Paiement Mobile Money reçu',
description: 'Kouassi Yao - 25,000 FCFA via Orange Money',
icon: Icons.phone_android,
color: Color(0xFFFF9800),
time: 'Il y a 3 min',
isNew: true,
),
Divider(height: 1),
ActivityItemWidget(
title: 'Nouveau membre validé',
description: 'Adjoua Marie inscrite depuis Abidjan',
icon: Icons.person_add,
color: AppTheme.successColor,
time: 'Il y a 15 min',
isNew: true,
),
Divider(height: 1),
ActivityItemWidget(
title: 'Relance automatique envoyée',
description: '12 SMS de rappel cotisations expédiés',
icon: Icons.sms,
color: AppTheme.infoColor,
time: 'Il y a 1h',
),
Divider(height: 1),
ActivityItemWidget(
title: 'Rapport OHADA généré',
description: 'Bilan financier T4 2024 exporté',
icon: Icons.description,
color: Color(0xFF795548),
time: 'Il y a 2h',
),
Divider(height: 1),
ActivityItemWidget(
title: 'Événement: Forte participation',
description: 'AG Extraordinaire - 89% de présence',
icon: Icons.trending_up,
color: AppTheme.successColor,
time: 'Il y a 3h',
),
Divider(height: 1),
ActivityItemWidget(
title: 'Alerte: Cotisations en retard',
description: '23 membres avec +30 jours de retard',
icon: Icons.warning,
color: AppTheme.warningColor,
time: 'Il y a 4h',
),
Divider(height: 1),
ActivityItemWidget(
title: 'Synchronisation réussie',
description: 'Données sauvegardées sur le cloud',
icon: Icons.cloud_done,
color: AppTheme.successColor,
time: 'Il y a 6h',
),
Divider(height: 1),
ActivityItemWidget(
title: 'Message diffusé',
description: 'Info COVID-19 envoyée à 1,247 membres',
icon: Icons.campaign,
color: Color(0xFF9C27B0),
time: 'Hier 18:30',
),
],
),
),
],
);
}
}

View File

@@ -1,218 +0,0 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../shared/theme/app_theme.dart';
class ActivityFeed extends StatelessWidget {
const ActivityFeed({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Activités récentes',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
TextButton(
onPressed: () {},
child: const Text('Voir tout'),
),
],
),
),
..._getActivities().map((activity) => _buildActivityItem(activity)),
],
),
);
}
Widget _buildActivityItem(ActivityItem activity) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: const BoxDecoration(
border: Border(
top: BorderSide(color: AppTheme.borderColor, width: 0.5),
),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: activity.color.withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: Icon(
activity.icon,
color: activity.color,
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 4),
Text(
activity.description,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.access_time,
size: 14,
color: AppTheme.textHint,
),
const SizedBox(width: 4),
Text(
_formatTime(activity.timestamp),
style: const TextStyle(
fontSize: 12,
color: AppTheme.textHint,
),
),
],
),
],
),
),
if (activity.actionRequired)
Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: AppTheme.errorColor,
shape: BoxShape.circle,
),
),
],
),
);
}
List<ActivityItem> _getActivities() {
final now = DateTime.now();
return [
ActivityItem(
title: 'Nouveau membre inscrit',
description: 'Marie Dupont a rejoint l\'association',
icon: Icons.person_add,
color: AppTheme.successColor,
timestamp: now.subtract(const Duration(hours: 2)),
actionRequired: false,
),
ActivityItem(
title: 'Cotisation en retard',
description: 'Pierre Martin - Cotisation échue depuis 5 jours',
icon: Icons.warning,
color: AppTheme.warningColor,
timestamp: now.subtract(const Duration(hours: 4)),
actionRequired: true,
),
ActivityItem(
title: 'Paiement reçu',
description: 'Jean Dubois - Cotisation annuelle 2024',
icon: Icons.payment,
color: AppTheme.primaryColor,
timestamp: now.subtract(const Duration(hours: 6)),
actionRequired: false,
),
ActivityItem(
title: 'Événement créé',
description: 'Assemblée générale 2024 - 15 mars 2024',
icon: Icons.event,
color: AppTheme.accentColor,
timestamp: now.subtract(const Duration(days: 1)),
actionRequired: false,
),
ActivityItem(
title: 'Mise à jour profil',
description: 'Sophie Bernard a modifié ses informations',
icon: Icons.edit,
color: AppTheme.infoColor,
timestamp: now.subtract(const Duration(days: 1, hours: 3)),
actionRequired: false,
),
ActivityItem(
title: 'Nouveau document',
description: 'Procès-verbal ajouté aux archives',
icon: Icons.file_upload,
color: AppTheme.secondaryColor,
timestamp: now.subtract(const Duration(days: 2)),
actionRequired: false,
),
];
}
String _formatTime(DateTime timestamp) {
final now = DateTime.now();
final difference = now.difference(timestamp);
if (difference.inMinutes < 60) {
return 'Il y a ${difference.inMinutes} min';
} else if (difference.inHours < 24) {
return 'Il y a ${difference.inHours}h';
} else if (difference.inDays == 1) {
return 'Hier';
} else if (difference.inDays < 7) {
return 'Il y a ${difference.inDays} jours';
} else {
return DateFormat('dd/MM/yyyy').format(timestamp);
}
}
}
class ActivityItem {
final String title;
final String description;
final IconData icon;
final Color color;
final DateTime timestamp;
final bool actionRequired;
ActivityItem({
required this.title,
required this.description,
required this.icon,
required this.color,
required this.timestamp,
this.actionRequired = false,
});
}

View File

@@ -1,335 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../../shared/theme/app_theme.dart';
class ChartCard extends StatelessWidget {
final String title;
final Widget chart;
final String? subtitle;
final VoidCallback? onTap;
const ChartCard({
super.key,
required this.title,
required this.chart,
this.subtitle,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textSecondary,
),
),
],
],
),
),
if (onTap != null)
const Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppTheme.textHint,
),
],
),
const SizedBox(height: 20),
SizedBox(
height: 200,
child: chart,
),
],
),
),
);
}
}
class MembershipChart extends StatelessWidget {
const MembershipChart({super.key});
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 200,
getDrawingHorizontalLine: (value) {
return FlLine(
color: AppTheme.borderColor.withOpacity(0.5),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 200,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
color: AppTheme.textHint,
fontSize: 12,
),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun'];
if (value.toInt() < months.length) {
return Text(
months[value.toInt()],
style: const TextStyle(
color: AppTheme.textHint,
fontSize: 12,
),
);
}
return const Text('');
},
),
),
),
borderData: FlBorderData(show: false),
minX: 0,
maxX: 5,
minY: 800,
maxY: 1400,
lineBarsData: [
LineChartBarData(
spots: const [
FlSpot(0, 1000),
FlSpot(1, 1050),
FlSpot(2, 1100),
FlSpot(3, 1180),
FlSpot(4, 1220),
FlSpot(5, 1247),
],
color: AppTheme.primaryColor,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: AppTheme.primaryColor,
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
AppTheme.primaryColor.withOpacity(0.3),
AppTheme.primaryColor.withOpacity(0.1),
AppTheme.primaryColor.withOpacity(0.0),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
],
),
);
}
}
class CategoryChart extends StatelessWidget {
const CategoryChart({super.key});
@override
Widget build(BuildContext context) {
return PieChart(
PieChartData(
sectionsSpace: 4,
centerSpaceRadius: 50,
sections: [
PieChartSectionData(
color: AppTheme.primaryColor,
value: 45,
title: 'Actifs\n45%',
radius: 60,
titleStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
PieChartSectionData(
color: AppTheme.secondaryColor,
value: 30,
title: 'Retraités\n30%',
radius: 60,
titleStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
PieChartSectionData(
color: AppTheme.accentColor,
value: 25,
title: 'Étudiants\n25%',
radius: 60,
titleStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
);
}
}
class RevenueChart extends StatelessWidget {
const RevenueChart({super.key});
@override
Widget build(BuildContext context) {
return BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: 15000,
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 5000,
getTitlesWidget: (value, meta) {
return Text(
'${(value / 1000).toInt()}k€',
style: const TextStyle(
color: AppTheme.textHint,
fontSize: 12,
),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
const months = ['J', 'F', 'M', 'A', 'M', 'J'];
if (value.toInt() < months.length) {
return Text(
months[value.toInt()],
style: const TextStyle(
color: AppTheme.textHint,
fontSize: 12,
),
);
}
return const Text('');
},
),
),
),
borderData: FlBorderData(show: false),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 5000,
getDrawingHorizontalLine: (value) {
return FlLine(
color: AppTheme.borderColor.withOpacity(0.5),
strokeWidth: 1,
);
},
),
barGroups: [
_buildBarGroup(0, 8000, AppTheme.primaryColor),
_buildBarGroup(1, 9500, AppTheme.primaryColor),
_buildBarGroup(2, 7800, AppTheme.primaryColor),
_buildBarGroup(3, 11200, AppTheme.primaryColor),
_buildBarGroup(4, 13500, AppTheme.primaryColor),
_buildBarGroup(5, 12800, AppTheme.primaryColor),
],
),
);
}
BarChartGroupData _buildBarGroup(int x, double y, Color color) {
return BarChartGroupData(
x: x,
barRods: [
BarChartRodData(
toY: y,
color: color,
width: 16,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(4),
),
),
],
);
}
}

View File

@@ -1,252 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../core/utils/responsive_utils.dart';
class ClickableKPICard extends StatefulWidget {
final String title;
final String value;
final String change;
final IconData icon;
final Color color;
final bool isPositiveChange;
final VoidCallback? onTap;
final String? actionText;
const ClickableKPICard({
super.key,
required this.title,
required this.value,
required this.change,
required this.icon,
required this.color,
this.isPositiveChange = true,
this.onTap,
this.actionText,
});
@override
State<ClickableKPICard> createState() => _ClickableKPICardState();
}
class _ClickableKPICardState extends State<ClickableKPICard>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Initialiser ResponsiveUtils
ResponsiveUtils.init(context);
return AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.onTap != null ? _handleTap : null,
onTapDown: widget.onTap != null ? (_) => _animationController.forward() : null,
onTapUp: widget.onTap != null ? (_) => _animationController.reverse() : null,
onTapCancel: widget.onTap != null ? () => _animationController.reverse() : null,
borderRadius: ResponsiveUtils.borderRadius(4),
child: Container(
padding: ResponsiveUtils.paddingAll(5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: ResponsiveUtils.borderRadius(4),
border: widget.onTap != null
? Border.all(
color: widget.color.withOpacity(0.2),
width: ResponsiveUtils.adaptive(
small: 1,
medium: 1.5,
large: 2,
),
)
: null,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 3.5.sp,
offset: Offset(0, 1.hp),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Icône et indicateur de changement
Flexible(
child: Row(
children: [
Container(
padding: ResponsiveUtils.paddingAll(2.5),
decoration: BoxDecoration(
color: widget.color.withOpacity(0.15),
borderRadius: ResponsiveUtils.borderRadius(2.5),
),
child: Icon(
widget.icon,
color: widget.color,
size: ResponsiveUtils.iconSize(5),
),
),
const Spacer(),
_buildChangeIndicator(),
],
),
),
SizedBox(height: 2.hp),
// Valeur principale
Flexible(
child: Text(
widget.value,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 4.5.fs,
medium: 4.2.fs,
large: 4.fs,
),
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(height: 0.5.hp),
// Titre et action
Flexible(
child: Row(
children: [
Expanded(
child: Text(
widget.title,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 3.fs,
medium: 2.8.fs,
large: 2.6.fs,
),
color: AppTheme.textSecondary,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (widget.onTap != null) ...[
SizedBox(width: 1.5.wp),
Flexible(
child: Container(
padding: ResponsiveUtils.paddingSymmetric(
horizontal: 1.5,
vertical: 0.3,
),
decoration: BoxDecoration(
color: widget.color.withOpacity(0.1),
borderRadius: ResponsiveUtils.borderRadius(2.5),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.actionText ?? 'Voir',
style: TextStyle(
color: widget.color,
fontSize: 2.5.fs,
fontWeight: FontWeight.w600,
),
),
SizedBox(width: 0.5.wp),
Icon(
Icons.arrow_forward_ios,
size: ResponsiveUtils.iconSize(2),
color: widget.color,
),
],
),
),
),
],
],
),
),
],
),
),
),
),
);
},
);
}
Widget _buildChangeIndicator() {
final changeColor = widget.isPositiveChange
? AppTheme.successColor
: AppTheme.errorColor;
final changeIcon = widget.isPositiveChange
? Icons.trending_up
: Icons.trending_down;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: changeColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
changeIcon,
size: 16,
color: changeColor,
),
const SizedBox(width: 4),
Text(
widget.change,
style: TextStyle(
color: changeColor,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
void _handleTap() {
HapticFeedback.lightImpact();
widget.onTap?.call();
}
}

View File

@@ -1,31 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
/// Widget d'en-tête de section réutilisable
///
/// Affiche un titre de section avec style cohérent
/// utilisé dans toutes les sections du dashboard.
class SectionHeaderWidget extends StatelessWidget {
/// Titre de la section
final String title;
/// Style de texte personnalisé (optionnel)
final TextStyle? textStyle;
const SectionHeaderWidget({
super.key,
required this.title,
this.textStyle,
});
@override
Widget build(BuildContext context) {
return Text(
title,
style: textStyle ?? Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
);
}
}

View File

@@ -0,0 +1,98 @@
/// Widget de tuile d'activité individuelle
/// Affiche une activité récente avec icône, titre et timestamp
library dashboard_activity_tile;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Modèle de données pour une activité récente
class DashboardActivity {
/// Titre principal de l'activité
final String title;
/// Description détaillée de l'activité
final String subtitle;
/// Icône représentative de l'activité
final IconData icon;
/// Couleur thématique de l'activité
final Color color;
/// Timestamp de l'activité
final String time;
/// Callback optionnel lors du tap sur l'activité
final VoidCallback? onTap;
/// Constructeur du modèle d'activité
const DashboardActivity({
required this.title,
required this.subtitle,
required this.icon,
required this.color,
required this.time,
this.onTap,
});
}
/// Widget de tuile d'activité
///
/// Affiche une activité récente avec :
/// - Avatar coloré avec icône thématique
/// - Titre et description de l'activité
/// - Timestamp relatif
/// - Design compact et lisible
/// - Support du tap pour détails
class DashboardActivityTile extends StatelessWidget {
/// Données de l'activité à afficher
final DashboardActivity activity;
/// Constructeur de la tuile d'activité
const DashboardActivityTile({
super.key,
required this.activity,
});
@override
Widget build(BuildContext context) {
return ListTile(
onTap: activity.onTap,
contentPadding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
leading: CircleAvatar(
radius: 16,
backgroundColor: activity.color.withOpacity(0.1),
child: Icon(
activity.icon,
color: activity.color,
size: 16,
),
),
title: Text(
activity.title,
style: TypographyTokens.bodySmall.copyWith(
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
activity.subtitle,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.onSurfaceVariant,
fontSize: 12,
),
),
trailing: Text(
activity.time,
style: TypographyTokens.labelSmall.copyWith(
color: ColorTokens.onSurfaceVariant,
fontSize: 11,
),
),
);
}
}

View File

@@ -0,0 +1,191 @@
/// Widget de menu latéral (drawer) du dashboard
/// Navigation principale de l'application
library dashboard_drawer;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Modèle de données pour un élément de menu
class DrawerMenuItem {
/// Icône de l'élément de menu
final IconData icon;
/// Titre de l'élément de menu
final String title;
/// Callback lors du tap sur l'élément
final VoidCallback? onTap;
/// Constructeur du modèle d'élément de menu
const DrawerMenuItem({
required this.icon,
required this.title,
this.onTap,
});
}
/// Widget de menu latéral
///
/// Affiche la navigation principale avec :
/// - Header avec profil utilisateur
/// - Menu de navigation structuré
/// - Actions secondaires
/// - Design Material avec gradient
class DashboardDrawer extends StatelessWidget {
/// Callback pour les actions de navigation
final Function(String route)? onNavigate;
/// Callback pour la déconnexion
final VoidCallback? onLogout;
/// Constructeur du menu latéral
const DashboardDrawer({
super.key,
this.onNavigate,
this.onLogout,
});
/// Génère la liste des éléments de menu principaux
List<DrawerMenuItem> _getMainMenuItems() {
return [
DrawerMenuItem(
icon: Icons.dashboard,
title: 'Dashboard',
onTap: () => onNavigate?.call('/dashboard'),
),
DrawerMenuItem(
icon: Icons.people,
title: 'Membres',
onTap: () => onNavigate?.call('/members'),
),
DrawerMenuItem(
icon: Icons.account_balance_wallet,
title: 'Cotisations',
onTap: () => onNavigate?.call('/cotisations'),
),
DrawerMenuItem(
icon: Icons.event,
title: 'Événements',
onTap: () => onNavigate?.call('/events'),
),
DrawerMenuItem(
icon: Icons.favorite,
title: 'Solidarité',
onTap: () => onNavigate?.call('/solidarity'),
),
];
}
/// Génère la liste des éléments de menu secondaires
List<DrawerMenuItem> _getSecondaryMenuItems() {
return [
DrawerMenuItem(
icon: Icons.analytics,
title: 'Rapports',
onTap: () => onNavigate?.call('/reports'),
),
DrawerMenuItem(
icon: Icons.settings,
title: 'Paramètres',
onTap: () => onNavigate?.call('/settings'),
),
DrawerMenuItem(
icon: Icons.help,
title: 'Aide',
onTap: () => onNavigate?.call('/help'),
),
];
}
@override
Widget build(BuildContext context) {
final mainItems = _getMainMenuItems();
final secondaryItems = _getSecondaryMenuItems();
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
_buildDrawerHeader(),
...mainItems.map((item) => _buildMenuItem(item)),
const Divider(),
...secondaryItems.map((item) => _buildMenuItem(item)),
const Divider(),
_buildLogoutItem(),
],
),
);
}
/// Construit l'en-tête du drawer avec profil utilisateur
Widget _buildDrawerHeader() {
return DrawerHeader(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [ColorTokens.primary, ColorTokens.secondary],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const CircleAvatar(
radius: 30,
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 35,
color: ColorTokens.primary,
),
),
const SizedBox(height: SpacingTokens.md),
Text(
'Utilisateur UnionFlow',
style: TypographyTokens.titleMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
Text(
'admin@unionflow.dev',
style: TypographyTokens.bodySmall.copyWith(
color: Colors.white.withOpacity(0.8),
),
),
],
),
);
}
/// Construit un élément de menu
Widget _buildMenuItem(DrawerMenuItem item) {
return ListTile(
leading: Icon(item.icon),
title: Text(
item.title,
style: TypographyTokens.bodyMedium,
),
onTap: item.onTap,
);
}
/// Construit l'élément de déconnexion
Widget _buildLogoutItem() {
return ListTile(
leading: const Icon(
Icons.logout,
color: ColorTokens.error,
),
title: Text(
'Déconnexion',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.error,
),
),
onTap: onLogout,
);
}
}

View File

@@ -0,0 +1,104 @@
/// Widget de section d'insights du dashboard
/// Affiche les métriques de performance dans une carte
library dashboard_insights_section;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
import 'dashboard_metric_row.dart';
/// Widget de section d'insights
///
/// Affiche les métriques de performance :
/// - Taux de cotisation
/// - Participation aux événements
/// - Demandes traitées
///
/// Chaque métrique peut être tapée pour plus de détails
class DashboardInsightsSection extends StatelessWidget {
/// Callback pour les actions sur les métriques
final Function(String metricType)? onMetricTap;
/// Liste des métriques à afficher
final List<DashboardMetric>? metrics;
/// Constructeur de la section d'insights
const DashboardInsightsSection({
super.key,
this.onMetricTap,
this.metrics,
});
/// Génère la liste des métriques par défaut
List<DashboardMetric> _getDefaultMetrics() {
return [
DashboardMetric(
label: 'Taux de cotisation',
value: '85%',
progress: 0.85,
color: ColorTokens.success,
onTap: () => onMetricTap?.call('cotisation_rate'),
),
DashboardMetric(
label: 'Participation événements',
value: '72%',
progress: 0.72,
color: ColorTokens.primary,
onTap: () => onMetricTap?.call('event_participation'),
),
DashboardMetric(
label: 'Demandes traitées',
value: '95%',
progress: 0.95,
color: ColorTokens.tertiary,
onTap: () => onMetricTap?.call('requests_processed'),
),
];
}
@override
Widget build(BuildContext context) {
final metricsToShow = metrics ?? _getDefaultMetrics();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Insights',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: SpacingTokens.md),
Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Performance ce mois-ci',
style: TypographyTokens.titleSmall.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: SpacingTokens.md),
...metricsToShow.map((metric) {
final isLast = metric == metricsToShow.last;
return Column(
children: [
DashboardMetricRow(metric: metric),
if (!isLast) const SizedBox(height: SpacingTokens.sm),
],
);
}).toList(),
],
),
),
),
],
);
}
}

View File

@@ -0,0 +1,94 @@
/// Widget de ligne de métrique avec barre de progression
/// Affiche une métrique avec label, valeur et indicateur visuel
library dashboard_metric_row;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Modèle de données pour une métrique
class DashboardMetric {
/// Label descriptif de la métrique
final String label;
/// Valeur formatée à afficher
final String value;
/// Progression entre 0.0 et 1.0
final double progress;
/// Couleur thématique de la métrique
final Color color;
/// Callback optionnel lors du tap sur la métrique
final VoidCallback? onTap;
/// Constructeur du modèle de métrique
const DashboardMetric({
required this.label,
required this.value,
required this.progress,
required this.color,
this.onTap,
});
}
/// Widget de ligne de métrique
///
/// Affiche une métrique avec :
/// - Label et valeur alignés horizontalement
/// - Barre de progression colorée
/// - Design compact et lisible
/// - Support du tap pour détails
class DashboardMetricRow extends StatelessWidget {
/// Données de la métrique à afficher
final DashboardMetric metric;
/// Constructeur de la ligne de métrique
const DashboardMetricRow({
super.key,
required this.metric,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: metric.onTap,
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.xs),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
metric.label,
style: TypographyTokens.bodySmall.copyWith(
fontWeight: FontWeight.w500,
),
),
Text(
metric.value,
style: TypographyTokens.labelLarge.copyWith(
fontWeight: FontWeight.w600,
color: metric.color,
),
),
],
),
const SizedBox(height: SpacingTokens.xs),
LinearProgressIndicator(
value: metric.progress,
backgroundColor: metric.color.withOpacity(0.1),
valueColor: AlwaysStoppedAnimation<Color>(metric.color),
minHeight: 4,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,102 @@
/// Widget de bouton d'action rapide individuel
/// Bouton stylisé pour les actions principales du dashboard
library dashboard_quick_action_button;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Modèle de données pour une action rapide
class DashboardQuickAction {
/// Icône représentative de l'action
final IconData icon;
/// Titre de l'action
final String title;
/// Sous-titre optionnel
final String? subtitle;
/// Couleur thématique du bouton
final Color color;
/// Callback lors du tap sur le bouton
final VoidCallback? onTap;
/// Constructeur du modèle d'action rapide
const DashboardQuickAction({
required this.icon,
required this.title,
this.subtitle,
required this.color,
this.onTap,
});
}
/// Widget de bouton d'action rapide
///
/// Affiche un bouton stylisé avec :
/// - Icône thématique
/// - Titre descriptif
/// - Couleur de fond subtile
/// - Design Material avec bordures arrondies
/// - Support du tap pour actions
class DashboardQuickActionButton extends StatelessWidget {
/// Données de l'action à afficher
final DashboardQuickAction action;
/// Constructeur du bouton d'action rapide
const DashboardQuickActionButton({
super.key,
required this.action,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: action.onTap,
style: ElevatedButton.styleFrom(
backgroundColor: action.color.withOpacity(0.1),
foregroundColor: action.color,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.sm,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
action.icon,
size: 18,
),
const SizedBox(height: 4),
Text(
action.title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 12,
),
textAlign: TextAlign.center,
),
if (action.subtitle != null) ...[
const SizedBox(height: 2),
Text(
action.subtitle!,
style: TextStyle(
fontSize: 10,
color: action.color.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
],
),
);
}
}

View File

@@ -0,0 +1,95 @@
/// Widget de grille d'actions rapides du dashboard
/// Affiche les actions principales dans une grille responsive
library dashboard_quick_actions_grid;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
import 'dashboard_quick_action_button.dart';
/// Widget de grille d'actions rapides
///
/// Affiche les actions principales dans une grille 2x2 :
/// - Ajouter un membre
/// - Enregistrer une cotisation
/// - Créer un événement
/// - Demande de solidarité
///
/// Chaque bouton déclenche une action spécifique
class DashboardQuickActionsGrid extends StatelessWidget {
/// Callback pour les actions rapides
final Function(String actionType)? onActionTap;
/// Liste des actions à afficher
final List<DashboardQuickAction>? actions;
/// Constructeur de la grille d'actions rapides
const DashboardQuickActionsGrid({
super.key,
this.onActionTap,
this.actions,
});
/// Génère la liste des actions rapides par défaut
List<DashboardQuickAction> _getDefaultActions() {
return [
DashboardQuickAction(
icon: Icons.person_add,
title: 'Ajouter Membre',
color: ColorTokens.primary,
onTap: () => onActionTap?.call('add_member'),
),
DashboardQuickAction(
icon: Icons.payment,
title: 'Cotisation',
color: ColorTokens.success,
onTap: () => onActionTap?.call('add_cotisation'),
),
DashboardQuickAction(
icon: Icons.event_note,
title: 'Événement',
color: ColorTokens.tertiary,
onTap: () => onActionTap?.call('create_event'),
),
DashboardQuickAction(
icon: Icons.volunteer_activism,
title: 'Solidarité',
color: ColorTokens.error,
onTap: () => onActionTap?.call('solidarity_request'),
),
];
}
@override
Widget build(BuildContext context) {
final actionsToShow = actions ?? _getDefaultActions();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions rapides',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: SpacingTokens.md),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: SpacingTokens.md,
mainAxisSpacing: SpacingTokens.md,
childAspectRatio: 2.2,
),
itemCount: actionsToShow.length,
itemBuilder: (context, index) {
return DashboardQuickActionButton(action: actionsToShow[index]);
},
),
],
);
}
}

View File

@@ -0,0 +1,98 @@
/// Widget de section d'activité récente du dashboard
/// Affiche les dernières activités dans une liste compacte
library dashboard_recent_activity_section;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
import 'dashboard_activity_tile.dart';
/// Widget de section d'activité récente
///
/// Affiche les dernières activités de l'union :
/// - Nouveaux membres
/// - Cotisations reçues
/// - Événements créés
/// - Demandes de solidarité
///
/// Chaque activité peut être tapée pour plus de détails
class DashboardRecentActivitySection extends StatelessWidget {
/// Callback pour les actions sur les activités
final Function(String activityId)? onActivityTap;
/// Liste des activités à afficher
final List<DashboardActivity>? activities;
/// Constructeur de la section d'activité récente
const DashboardRecentActivitySection({
super.key,
this.onActivityTap,
this.activities,
});
/// Génère la liste des activités récentes par défaut
List<DashboardActivity> _getDefaultActivities() {
return [
DashboardActivity(
title: 'Nouveau membre ajouté',
subtitle: 'Marie Dupont a rejoint l\'union',
icon: Icons.person_add,
color: ColorTokens.primary,
time: 'Il y a 2h',
onTap: () => onActivityTap?.call('member_added_001'),
),
DashboardActivity(
title: 'Cotisation reçue',
subtitle: 'Paiement de 50€ de Jean Martin',
icon: Icons.payment,
color: ColorTokens.success,
time: 'Il y a 4h',
onTap: () => onActivityTap?.call('cotisation_002'),
),
DashboardActivity(
title: 'Événement créé',
subtitle: 'Assemblée générale programmée',
icon: Icons.event,
color: ColorTokens.tertiary,
time: 'Hier',
onTap: () => onActivityTap?.call('event_003'),
),
];
}
@override
Widget build(BuildContext context) {
final activitiesToShow = activities ?? _getDefaultActivities();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Activité récente',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: SpacingTokens.md),
Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
child: Column(
children: activitiesToShow.map((activity) {
final isLast = activity == activitiesToShow.last;
return Column(
children: [
DashboardActivityTile(activity: activity),
if (!isLast) const Divider(height: 1),
],
);
}).toList(),
),
),
),
],
);
}
}

View File

@@ -0,0 +1,94 @@
/// Widget de carte de statistique individuelle
/// Affiche une métrique avec icône, valeur et titre
library dashboard_stats_card;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Modèle de données pour une statistique
class DashboardStat {
/// Icône représentative de la statistique
final IconData icon;
/// Valeur numérique à afficher
final String value;
/// Titre descriptif de la statistique
final String title;
/// Couleur thématique de la carte
final Color color;
/// Callback optionnel lors du tap sur la carte
final VoidCallback? onTap;
/// Constructeur du modèle de statistique
const DashboardStat({
required this.icon,
required this.value,
required this.title,
required this.color,
this.onTap,
});
}
/// Widget de carte de statistique
///
/// Affiche une métrique individuelle avec :
/// - Icône colorée thématique
/// - Valeur numérique mise en évidence
/// - Titre descriptif
/// - Design Material avec élévation subtile
/// - Support du tap pour navigation
class DashboardStatsCard extends StatelessWidget {
/// Données de la statistique à afficher
final DashboardStat stat;
/// Constructeur de la carte de statistique
const DashboardStatsCard({
super.key,
required this.stat,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 1,
child: InkWell(
onTap: stat.onTap,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
stat.icon,
size: 28,
color: stat.color,
),
const SizedBox(height: SpacingTokens.sm),
Text(
stat.value,
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
color: stat.color,
),
),
const SizedBox(height: SpacingTokens.xs),
Text(
stat.title,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,99 @@
/// Widget de grille de statistiques du dashboard
/// Affiche les métriques principales dans une grille responsive
library dashboard_stats_grid;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
import 'dashboard_stats_card.dart';
/// Widget de grille de statistiques
///
/// Affiche les statistiques principales dans une grille 2x2 :
/// - Membres actifs
/// - Cotisations du mois
/// - Événements programmés
/// - Demandes de solidarité
///
/// Chaque carte est interactive et peut déclencher une navigation
class DashboardStatsGrid extends StatelessWidget {
/// Callback pour les actions sur les statistiques
final Function(String statType)? onStatTap;
/// Liste des statistiques à afficher
final List<DashboardStat>? stats;
/// Constructeur de la grille de statistiques
const DashboardStatsGrid({
super.key,
this.onStatTap,
this.stats,
});
/// Génère la liste des statistiques par défaut
List<DashboardStat> _getDefaultStats() {
return [
DashboardStat(
icon: Icons.people,
value: '25',
title: 'Membres',
color: ColorTokens.primary,
onTap: () => onStatTap?.call('members'),
),
DashboardStat(
icon: Icons.account_balance_wallet,
value: '15',
title: 'Cotisations',
color: ColorTokens.success,
onTap: () => onStatTap?.call('cotisations'),
),
DashboardStat(
icon: Icons.event,
value: '8',
title: 'Événements',
color: ColorTokens.tertiary,
onTap: () => onStatTap?.call('events'),
),
DashboardStat(
icon: Icons.favorite,
value: '3',
title: 'Solidarité',
color: ColorTokens.error,
onTap: () => onStatTap?.call('solidarity'),
),
];
}
@override
Widget build(BuildContext context) {
final statsToShow = stats ?? _getDefaultStats();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Statistiques',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: SpacingTokens.md),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: SpacingTokens.md,
mainAxisSpacing: SpacingTokens.md,
childAspectRatio: 1.4,
),
itemCount: statsToShow.length,
itemBuilder: (context, index) {
return DashboardStatsCard(stat: statsToShow[index]);
},
),
],
);
}
}

View File

@@ -0,0 +1,70 @@
/// Widget de section de bienvenue du dashboard
/// Affiche un message d'accueil avec gradient et design moderne
library dashboard_welcome_section;
import 'package:flutter/material.dart';
import '../../../../core/design_system/tokens/color_tokens.dart';
import '../../../../core/design_system/tokens/spacing_tokens.dart';
import '../../../../core/design_system/tokens/typography_tokens.dart';
/// Widget de section de bienvenue
///
/// Affiche un message d'accueil personnalisé avec :
/// - Gradient de fond élégant
/// - Typographie hiérarchisée
/// - Design responsive et moderne
class DashboardWelcomeSection extends StatelessWidget {
/// Titre principal de la section
final String title;
/// Sous-titre descriptif
final String subtitle;
/// Constructeur du widget de bienvenue
const DashboardWelcomeSection({
super.key,
this.title = 'Bienvenue sur UnionFlow',
this.subtitle = 'Votre plateforme de gestion d\'union familiale',
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
ColorTokens.primary.withOpacity(0.1),
ColorTokens.secondary.withOpacity(0.05),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(
color: ColorTokens.outline.withOpacity(0.1),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.w700,
color: ColorTokens.primary,
),
),
const SizedBox(height: SpacingTokens.xs),
Text(
subtitle,
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.onSurfaceVariant,
),
),
],
),
);
}
}

View File

@@ -1,289 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
/// Widget de carte KPI réutilisable avec détails enrichis
///
/// Affiche un indicateur de performance clé avec:
/// - Icône et badge de tendance coloré
/// - Valeur principale avec objectif optionnel
/// - Titre avec période
/// - Description détaillée
/// - Points de détail sous forme de puces
/// - Horodatage de dernière mise à jour
class KPICardWidget extends StatelessWidget {
/// Titre de l'indicateur
final String title;
/// Valeur principale affichée
final String value;
/// Changement/tendance (ex: "+5.2%", "-3.1%")
final String change;
/// Icône représentative
final IconData icon;
/// Couleur thématique de la carte
final Color color;
/// Description détaillée optionnelle
final String? subtitle;
/// Période de référence (ex: "30j", "Mois")
final String? period;
/// Objectif cible optionnel
final String? target;
/// Horodatage de dernière mise à jour
final String? lastUpdate;
/// Liste de détails supplémentaires (max 3)
final List<String>? details;
const KPICardWidget({
super.key,
required this.title,
required this.value,
required this.change,
required this.icon,
required this.color,
this.subtitle,
this.period,
this.target,
this.lastUpdate,
this.details,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête avec icône et badge de tendance
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 20,
),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _getChangeColor(change).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_getChangeIcon(change),
color: _getChangeColor(change),
size: 12,
),
const SizedBox(width: 4),
Text(
change,
style: TextStyle(
color: _getChangeColor(change),
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
const SizedBox(height: 12),
// Valeur principale
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
),
if (target != null)
Text(
'/ $target',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppTheme.textSecondary,
),
),
],
),
const SizedBox(height: 4),
// Titre et période
Row(
children: [
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
),
if (period != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
period!,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: color,
),
),
),
],
),
// Description détaillée
if (subtitle != null) ...[
const SizedBox(height: 6),
Text(
subtitle!,
style: const TextStyle(
fontSize: 11,
color: AppTheme.textSecondary,
height: 1.3,
),
),
],
// Détails supplémentaires sous forme de puces
if (details != null && details!.isNotEmpty) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.05),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: color.withOpacity(0.1),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: details!.take(3).map((detail) => Padding(
padding: const EdgeInsets.only(bottom: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 4),
width: 4,
height: 4,
decoration: BoxDecoration(
color: color.withOpacity(0.6),
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Expanded(
child: Text(
detail,
style: TextStyle(
fontSize: 10,
color: AppTheme.textSecondary.withOpacity(0.8),
height: 1.2,
),
),
),
],
),
)).toList(),
),
),
],
// Dernière mise à jour
if (lastUpdate != null) ...[
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.access_time,
size: 10,
color: AppTheme.textSecondary.withOpacity(0.5),
),
const SizedBox(width: 4),
Text(
'Mis à jour: $lastUpdate',
style: TextStyle(
fontSize: 9,
color: AppTheme.textSecondary.withOpacity(0.5),
fontStyle: FontStyle.italic,
),
),
],
),
],
],
),
);
}
/// Détermine la couleur du badge de changement selon la valeur
Color _getChangeColor(String change) {
if (change.startsWith('+')) {
return AppTheme.successColor;
} else if (change.startsWith('-')) {
return AppTheme.errorColor;
} else {
return AppTheme.textSecondary;
}
}
/// Détermine l'icône du badge de changement selon la valeur
IconData _getChangeIcon(String change) {
if (change.startsWith('+')) {
return Icons.trending_up;
} else if (change.startsWith('-')) {
return Icons.trending_down;
} else {
return Icons.trending_flat;
}
}
}

View File

@@ -1,171 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
import 'kpi_card_widget.dart';
/// Widget de section des cartes KPI principales
///
/// Affiche les 8 indicateurs clés de performance principaux
/// en une seule colonne pour optimiser l'utilisation de l'espace écran.
/// Chaque KPI contient des détails enrichis et des informations contextuelles.
class KPICardsWidget extends StatelessWidget {
const KPICardsWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Indicateurs clés de performance',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
// Indicateurs principaux - Une seule colonne pour exploiter toute la largeur
KPICardWidget(
title: 'Membres Actifs',
value: '1,247',
change: '+5.2%',
icon: Icons.people,
color: AppTheme.primaryColor,
subtitle: 'Base de cotisants actifs avec droits de vote et participation aux décisions',
period: '30j',
target: '1,300',
lastUpdate: 'il y a 2h',
details: const [
'892 membres à jour de cotisation (71.5%)',
'355 nouveaux membres cette année',
'23 membres en période d\'essai de 3 mois',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Revenus Totaux',
value: '2,845,000 FCFA',
change: '+12.8%',
icon: Icons.account_balance_wallet,
color: AppTheme.successColor,
subtitle: 'Ensemble des revenus générés incluant cotisations, événements et subventions',
period: 'Mois',
target: '3,200,000 FCFA',
lastUpdate: 'il y a 1h',
details: const [
'1,950,000 FCFA de cotisations mensuelles (68.5%)',
'645,000 FCFA d\'activités et événements (22.7%)',
'250,000 FCFA de dons et subventions (8.8%)',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Événements Actifs',
value: '23',
change: '+3',
icon: Icons.event,
color: AppTheme.accentColor,
subtitle: 'Événements planifiés, formations professionnelles et activités sociales',
period: 'Mois',
target: '25',
lastUpdate: 'il y a 3h',
details: const [
'8 formations professionnelles et techniques',
'9 événements sociaux et culturels',
'6 assemblées générales et réunions',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Taux de Participation',
value: '78.3%',
change: '+2.1%',
icon: Icons.groups,
color: const Color(0xFF2196F3), // Blue
subtitle: 'Pourcentage de membres participant activement aux événements et décisions',
period: 'Trim.',
target: '85%',
lastUpdate: 'il y a 4h',
details: const [
'158 membres en retard de paiement',
'45,000 FCFA de frais de relance économisés',
'Amélioration de 12% par rapport au trimestre précédent',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Nouveaux Membres (30j)',
value: '47',
change: '+18.5%',
icon: Icons.person_add,
color: const Color(0xFF9C27B0), // Purple
subtitle: 'Nouvelles adhésions validées par le comité d\'admission',
period: '30j',
target: '50',
lastUpdate: 'il y a 30min',
details: const [
'28 adhésions individuelles (59.6%)',
'12 adhésions familiales (25.5%)',
'7 adhésions d\'entreprises partenaires (14.9%)',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Montant en Attente',
value: '785,000 FCFA',
change: '-5.2%',
icon: Icons.schedule,
color: AppTheme.warningColor,
subtitle: 'Montants promis en attente d\'encaissement ou de validation administrative',
period: 'Total',
lastUpdate: 'il y a 1h',
details: const [
'450,000 FCFA de promesses de dons (57.3%)',
'235,000 FCFA de cotisations promises (29.9%)',
'100,000 FCFA de subventions en cours (12.8%)',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Cotisations en Retard',
value: '156',
change: '+8.3%',
icon: Icons.access_time,
color: AppTheme.errorColor,
subtitle: 'Membres en situation d\'impayé nécessitant un suivi personnalisé',
period: '+30j',
lastUpdate: 'il y a 2h',
details: const [
'89 retards de 1-3 mois (57.1%)',
'45 retards de 3-6 mois (28.8%)',
'22 retards de plus de 6 mois (14.1%)',
],
),
const SizedBox(height: 12),
KPICardWidget(
title: 'Score Global de Performance',
value: '85/100',
change: '+3 pts',
icon: Icons.assessment,
color: const Color(0xFF00BCD4), // Cyan
subtitle: 'Évaluation globale basée sur 15 indicateurs de santé organisationnelle',
period: 'Mois',
target: '90/100',
lastUpdate: 'il y a 6h',
details: const [
'Finances: 92/100 (Excellent)',
'Participation: 78/100 (Bon)',
'Gouvernance: 85/100 (Très bon)',
],
),
],
);
}
}

View File

@@ -1,116 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../shared/theme/app_theme.dart';
class KPICard extends StatelessWidget {
final String title;
final String value;
final String change;
final IconData icon;
final Color color;
final bool isPositiveChange;
const KPICard({
super.key,
required this.title,
required this.value,
required this.change,
required this.icon,
required this.color,
this.isPositiveChange = true,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const Spacer(),
_buildChangeIndicator(),
],
),
const SizedBox(height: 20),
Text(
value,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 16,
color: AppTheme.textSecondary,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Widget _buildChangeIndicator() {
final changeColor = isPositiveChange
? AppTheme.successColor
: AppTheme.errorColor;
final changeIcon = isPositiveChange
? Icons.trending_up
: Icons.trending_down;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: changeColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
changeIcon,
size: 16,
color: changeColor,
),
const SizedBox(width: 4),
Text(
change,
style: TextStyle(
color: changeColor,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}

View File

@@ -1,281 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../core/utils/responsive_utils.dart';
class NavigationCards extends StatelessWidget {
final Function(int)? onNavigateToTab;
const NavigationCards({
super.key,
this.onNavigateToTab,
});
@override
Widget build(BuildContext context) {
ResponsiveUtils.init(context);
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(20),
child: Row(
children: [
Icon(
Icons.dashboard_customize,
color: AppTheme.primaryColor,
size: 20,
),
SizedBox(width: 8),
Text(
'Accès rapide aux modules',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.1,
children: [
_buildNavigationCard(
context,
title: 'Membres',
subtitle: '1,247 membres',
icon: Icons.people_rounded,
color: AppTheme.secondaryColor,
onTap: () => _navigateToModule(context, 1, 'Membres'),
badge: '+5 cette semaine',
),
_buildNavigationCard(
context,
title: 'Cotisations',
subtitle: '89.5% à jour',
icon: Icons.payment_rounded,
color: AppTheme.accentColor,
onTap: () => _navigateToModule(context, 2, 'Cotisations'),
badge: '15 en retard',
badgeColor: AppTheme.warningColor,
),
_buildNavigationCard(
context,
title: 'Événements',
subtitle: '3 à venir',
icon: Icons.event_rounded,
color: AppTheme.warningColor,
onTap: () => _navigateToModule(context, 3, 'Événements'),
badge: 'AG dans 5 jours',
),
_buildNavigationCard(
context,
title: 'Finances',
subtitle: '€45,890',
icon: Icons.account_balance_rounded,
color: AppTheme.primaryColor,
onTap: () => _navigateToModule(context, 4, 'Finances'),
badge: '+12.8% ce mois',
badgeColor: AppTheme.successColor,
),
],
),
),
],
),
);
}
Widget _buildNavigationCard(
BuildContext context, {
required String title,
required String subtitle,
required IconData icon,
required Color color,
required VoidCallback onTap,
String? badge,
Color? badgeColor,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
HapticFeedback.lightImpact();
onTap();
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(
color: color.withOpacity(0.2),
width: 1,
),
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
color.withOpacity(0.05),
color.withOpacity(0.02),
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header avec icône et badge
Row(
children: [
Flexible(
child: Container(
width: ResponsiveUtils.iconSize(8),
height: ResponsiveUtils.iconSize(8),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(ResponsiveUtils.iconSize(4)),
),
child: Icon(
icon,
color: color,
size: ResponsiveUtils.iconSize(4.5),
),
),
),
const Spacer(),
if (badge != null)
Flexible(
child: Container(
padding: ResponsiveUtils.paddingSymmetric(
horizontal: 1.5,
vertical: 0.3,
),
decoration: BoxDecoration(
color: (badgeColor ?? AppTheme.successColor).withOpacity(0.1),
borderRadius: ResponsiveUtils.borderRadius(2),
border: Border.all(
color: (badgeColor ?? AppTheme.successColor).withOpacity(0.3),
width: 0.5,
),
),
child: Text(
badge,
style: TextStyle(
color: badgeColor ?? AppTheme.successColor,
fontSize: 2.5.fs,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
const Spacer(),
// Contenu principal
Text(
title,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 4.fs,
medium: 3.8.fs,
large: 3.6.fs,
),
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 1.hp),
Text(
subtitle,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 3.2.fs,
medium: 3.fs,
large: 2.8.fs,
),
color: AppTheme.textSecondary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
// Flèche d'action
Row(
children: [
Text(
'Gérer',
style: TextStyle(
fontSize: 12,
color: color,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 4),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: color,
),
],
),
],
),
),
),
);
}
void _navigateToModule(BuildContext context, int tabIndex, String moduleName) {
// Si onNavigateToTab est fourni, l'utiliser pour naviguer vers l'onglet
if (onNavigateToTab != null) {
onNavigateToTab!(tabIndex);
} else {
// Sinon, afficher un message temporaire
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Navigation vers $moduleName'),
backgroundColor: AppTheme.primaryColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
action: SnackBarAction(
label: 'OK',
textColor: Colors.white,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}
}
}

View File

@@ -1,214 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../core/utils/responsive_utils.dart';
class QuickActionsGrid extends StatelessWidget {
const QuickActionsGrid({super.key});
@override
Widget build(BuildContext context) {
ResponsiveUtils.init(context);
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(20),
child: Text(
'Actions rapides',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.2,
children: _getQuickActions(context),
),
),
],
),
);
}
List<Widget> _getQuickActions(BuildContext context) {
final actions = [
QuickAction(
title: 'Nouveau membre',
description: 'Ajouter un membre',
icon: Icons.person_add,
color: AppTheme.primaryColor,
onTap: () => _showAction(context, 'Nouveau membre'),
),
QuickAction(
title: 'Créer événement',
description: 'Organiser un événement',
icon: Icons.event_available,
color: AppTheme.secondaryColor,
onTap: () => _showAction(context, 'Créer événement'),
),
QuickAction(
title: 'Suivi cotisations',
description: 'Gérer les cotisations',
icon: Icons.payment,
color: AppTheme.accentColor,
onTap: () => _showAction(context, 'Suivi cotisations'),
),
QuickAction(
title: 'Rapports',
description: 'Générer des rapports',
icon: Icons.analytics,
color: AppTheme.infoColor,
onTap: () => _showAction(context, 'Rapports'),
),
QuickAction(
title: 'Messages',
description: 'Envoyer des notifications',
icon: Icons.message,
color: AppTheme.warningColor,
onTap: () => _showAction(context, 'Messages'),
),
QuickAction(
title: 'Documents',
description: 'Gérer les documents',
icon: Icons.folder,
color: Color(0xFF9C27B0),
onTap: () => _showAction(context, 'Documents'),
),
];
return actions.map((action) => _buildActionCard(action)).toList();
}
Widget _buildActionCard(QuickAction action) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: action.onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(
color: action.color.withOpacity(0.2),
width: 1,
),
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
width: ResponsiveUtils.iconSize(12),
height: ResponsiveUtils.iconSize(12),
decoration: BoxDecoration(
color: action.color.withOpacity(0.15),
borderRadius: BorderRadius.circular(ResponsiveUtils.iconSize(6)),
),
child: Icon(
action.icon,
color: action.color,
size: ResponsiveUtils.iconSize(6),
),
),
),
SizedBox(height: 2.hp),
Flexible(
child: Text(
action.title,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 3.5.fs,
medium: 3.2.fs,
large: 3.fs,
),
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(height: 0.5.hp),
Flexible(
child: Text(
action.description,
style: TextStyle(
fontSize: ResponsiveUtils.adaptive(
small: 2.8.fs,
medium: 2.6.fs,
large: 2.4.fs,
),
color: AppTheme.textSecondary,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
);
}
void _showAction(BuildContext context, String actionName) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$actionName - En cours de développement'),
backgroundColor: AppTheme.primaryColor,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
action: SnackBarAction(
label: 'OK',
textColor: Colors.white,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}
}
class QuickAction {
final String title;
final String description;
final IconData icon;
final Color color;
final VoidCallback onTap;
QuickAction({
required this.title,
required this.description,
required this.icon,
required this.color,
required this.onTap,
});
}

View File

@@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../shared/theme/app_theme.dart';
/// Widget de section d'accueil personnalisé pour le dashboard
///
/// Affiche un message de bienvenue avec un gradient coloré et une icône.
/// Conçu pour donner une impression chaleureuse et professionnelle à l'utilisateur.
class WelcomeSectionWidget extends StatelessWidget {
/// Titre principal affiché (par défaut "Bonjour !")
final String title;
/// Sous-titre descriptif (par défaut "Voici un aperçu de votre association")
final String subtitle;
/// Icône affichée à droite (par défaut Icons.dashboard)
final IconData icon;
/// Couleurs du gradient (par défaut primaryColor vers primaryLight)
final List<Color>? gradientColors;
const WelcomeSectionWidget({
super.key,
this.title = 'Bonjour !',
this.subtitle = 'Voici un aperçu de votre association',
this.icon = Icons.dashboard,
this.gradientColors,
});
@override
Widget build(BuildContext context) {
final colors = gradientColors ?? [AppTheme.primaryColor, AppTheme.primaryLight];
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: colors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
subtitle,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 16,
),
),
],
),
),
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(30),
),
child: Icon(
icon,
color: Colors.white,
size: 30,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,17 @@
/// Fichier d'index pour tous les widgets du dashboard
/// Facilite les imports et maintient une API propre
library dashboard_widgets;
// === WIDGETS DE SECTION ===
export 'dashboard_welcome_section.dart';
export 'dashboard_stats_grid.dart';
export 'dashboard_quick_actions_grid.dart';
export 'dashboard_recent_activity_section.dart';
export 'dashboard_insights_section.dart';
export 'dashboard_drawer.dart';
// === WIDGETS ATOMIQUES ===
export 'dashboard_stats_card.dart';
export 'dashboard_quick_action_button.dart';
export 'dashboard_activity_tile.dart';
export 'dashboard_metric_row.dart';