675 lines
19 KiB
Dart
675 lines
19 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:fl_chart/fl_chart.dart';
|
|
import '../../../../shared/theme/app_theme.dart';
|
|
|
|
class DashboardPage extends StatelessWidget {
|
|
const DashboardPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.backgroundLight,
|
|
appBar: AppBar(
|
|
title: const Text('Tableau de bord'),
|
|
backgroundColor: AppTheme.primaryColor,
|
|
elevation: 0,
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.notifications_outlined),
|
|
onPressed: () {},
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.settings_outlined),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Message de bienvenue
|
|
_buildWelcomeSection(context),
|
|
const SizedBox(height: 24),
|
|
|
|
// Cartes KPI principales
|
|
_buildKPICards(context),
|
|
const SizedBox(height: 24),
|
|
|
|
// Graphiques et statistiques
|
|
_buildChartsSection(context),
|
|
const SizedBox(height: 24),
|
|
|
|
// Actions rapides
|
|
_buildQuickActions(context),
|
|
const SizedBox(height: 24),
|
|
|
|
// Activités récentes
|
|
_buildRecentActivities(context),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildWelcomeSection(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [AppTheme.primaryColor, AppTheme.primaryLight],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Bonjour !',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Voici un aperçu de votre association',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
child: const Icon(
|
|
Icons.dashboard,
|
|
color: Colors.white,
|
|
size: 30,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildKPICards(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Indicateurs clés',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildKPICard(
|
|
context,
|
|
'Membres',
|
|
'1,247',
|
|
'+5.2%',
|
|
Icons.people,
|
|
AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildKPICard(
|
|
context,
|
|
'Revenus',
|
|
'€45,890',
|
|
'+12.8%',
|
|
Icons.euro,
|
|
AppTheme.successColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildKPICard(
|
|
context,
|
|
'Événements',
|
|
'23',
|
|
'+3',
|
|
Icons.event,
|
|
AppTheme.accentColor,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildKPICard(
|
|
context,
|
|
'Cotisations',
|
|
'89.5%',
|
|
'+2.1%',
|
|
Icons.payments,
|
|
AppTheme.infoColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildKPICard(
|
|
BuildContext context,
|
|
String title,
|
|
String value,
|
|
String change,
|
|
IconData icon,
|
|
Color color,
|
|
) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: color,
|
|
size: 20,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.successColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
change,
|
|
style: const TextStyle(
|
|
color: AppTheme.successColor,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
value,
|
|
style: const TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
color: AppTheme.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildChartsSection(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Analyses',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildLineChart(context),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildPieChart(context),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildLineChart(BuildContext context) {
|
|
return Container(
|
|
height: 200,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Évolution des membres',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: LineChart(
|
|
LineChartData(
|
|
gridData: const FlGridData(show: false),
|
|
titlesData: const FlTitlesData(show: false),
|
|
borderData: FlBorderData(show: false),
|
|
lineBarsData: [
|
|
LineChartBarData(
|
|
spots: const [
|
|
FlSpot(0, 1000),
|
|
FlSpot(1, 1050),
|
|
FlSpot(2, 1100),
|
|
FlSpot(3, 1180),
|
|
FlSpot(4, 1247),
|
|
],
|
|
color: AppTheme.primaryColor,
|
|
barWidth: 3,
|
|
isStrokeCapRound: true,
|
|
dotData: const FlDotData(show: false),
|
|
belowBarData: BarAreaData(
|
|
show: true,
|
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPieChart(BuildContext context) {
|
|
return Container(
|
|
height: 200,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Répartition des membres',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Expanded(
|
|
child: PieChart(
|
|
PieChartData(
|
|
sectionsSpace: 0,
|
|
centerSpaceRadius: 40,
|
|
sections: [
|
|
PieChartSectionData(
|
|
color: AppTheme.primaryColor,
|
|
value: 45,
|
|
title: '45%',
|
|
radius: 50,
|
|
titleStyle: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
PieChartSectionData(
|
|
color: AppTheme.secondaryColor,
|
|
value: 30,
|
|
title: '30%',
|
|
radius: 50,
|
|
titleStyle: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
PieChartSectionData(
|
|
color: AppTheme.accentColor,
|
|
value: 25,
|
|
title: '25%',
|
|
radius: 50,
|
|
titleStyle: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildQuickActions(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Actions rapides',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildActionCard(
|
|
context,
|
|
'Nouveau membre',
|
|
'Ajouter un membre',
|
|
Icons.person_add,
|
|
AppTheme.primaryColor,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildActionCard(
|
|
context,
|
|
'Créer événement',
|
|
'Organiser un événement',
|
|
Icons.event_available,
|
|
AppTheme.secondaryColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildActionCard(
|
|
context,
|
|
'Suivi cotisations',
|
|
'Gérer les cotisations',
|
|
Icons.payment,
|
|
AppTheme.accentColor,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildActionCard(
|
|
context,
|
|
'Rapports',
|
|
'Générer des rapports',
|
|
Icons.analytics,
|
|
AppTheme.infoColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildActionCard(
|
|
BuildContext context,
|
|
String title,
|
|
String subtitle,
|
|
IconData icon,
|
|
Color color,
|
|
) {
|
|
return InkWell(
|
|
onTap: () {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('$title - En cours de développement'),
|
|
backgroundColor: color,
|
|
),
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: color.withOpacity(0.2)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: color,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: AppTheme.textSecondary,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRecentActivities(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Activités récentes',
|
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('Voir tout'),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_buildActivityItem(
|
|
'Nouveau membre inscrit',
|
|
'Marie Dupont a rejoint l\'association',
|
|
Icons.person_add,
|
|
AppTheme.successColor,
|
|
'Il y a 2h',
|
|
),
|
|
const Divider(height: 1),
|
|
_buildActivityItem(
|
|
'Cotisation reçue',
|
|
'Jean Martin a payé sa cotisation annuelle',
|
|
Icons.payment,
|
|
AppTheme.primaryColor,
|
|
'Il y a 4h',
|
|
),
|
|
const Divider(height: 1),
|
|
_buildActivityItem(
|
|
'Événement créé',
|
|
'Assemblée générale 2024 programmée',
|
|
Icons.event,
|
|
AppTheme.accentColor,
|
|
'Hier',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildActivityItem(
|
|
String title,
|
|
String description,
|
|
IconData icon,
|
|
Color color,
|
|
String time,
|
|
) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: color,
|
|
size: 16,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppTheme.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
description,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: AppTheme.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
time,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: AppTheme.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |