Authentification stable - WIP
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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) {},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user