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

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

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

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

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

- Nettoyage: fichiers temporaires supprimés

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

View File

@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
/// Widget de graphique pour le dashboard
class DashboardChartWidget extends StatelessWidget {
@@ -20,14 +21,13 @@ class DashboardChartWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
SizedBox(
height: height,
child: BlocBuilder<DashboardBloc, DashboardState>(
@@ -54,23 +54,16 @@ class DashboardChartWidget extends StatelessWidget {
Widget _buildHeader() {
return Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.royalBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: Icon(
_getChartIcon(),
color: DashboardTheme.royalBlue,
size: 20,
),
Icon(
_getChartIcon(),
color: AppColors.primaryGreen,
size: 18,
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: DashboardTheme.titleMedium,
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
),
),
],
@@ -97,22 +90,22 @@ class DashboardChartWidget extends StatelessWidget {
centerSpaceRadius: 40,
sections: [
PieChartSectionData(
color: DashboardTheme.success,
color: AppColors.success,
value: stats.activeMembers.toDouble(),
title: '${stats.activeMembers}',
radius: 50,
titleStyle: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white,
titleStyle: AppTypography.badgeText.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
PieChartSectionData(
color: DashboardTheme.grey300,
color: AppColors.lightBorder,
value: (stats.totalMembers - stats.activeMembers).toDouble(),
title: '${stats.totalMembers - stats.activeMembers}',
radius: 45,
titleStyle: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey700,
titleStyle: AppTypography.badgeText.copyWith(
color: AppColors.textSecondaryLight,
fontWeight: FontWeight.bold,
),
),
@@ -130,7 +123,7 @@ class DashboardChartWidget extends StatelessWidget {
horizontalInterval: stats.totalContributionAmount / 4,
getDrawingHorizontalLine: (value) {
return const FlLine(
color: DashboardTheme.grey200,
color: AppColors.lightBorder,
strokeWidth: 1,
);
},
@@ -149,7 +142,7 @@ class DashboardChartWidget extends StatelessWidget {
if (value.toInt() >= 0 && value.toInt() < months.length) {
return Text(
months[value.toInt()],
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 8),
);
}
return const Text('');
@@ -164,7 +157,7 @@ class DashboardChartWidget extends StatelessWidget {
getTitlesWidget: (double value, TitleMeta meta) {
return Text(
'${(value / 1000).toStringAsFixed(0)}K',
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 8),
);
},
),
@@ -181,8 +174,8 @@ class DashboardChartWidget extends StatelessWidget {
isCurved: true,
gradient: const LinearGradient(
colors: [
DashboardTheme.tealBlue,
DashboardTheme.royalBlue,
AppColors.brandGreen,
AppColors.primaryGreen,
],
),
barWidth: 3,
@@ -192,8 +185,8 @@ class DashboardChartWidget extends StatelessWidget {
show: true,
gradient: LinearGradient(
colors: [
DashboardTheme.tealBlue.withOpacity(0.3),
DashboardTheme.royalBlue.withOpacity(0.1),
AppColors.brandGreen.withOpacity(0.3),
AppColors.primaryGreen.withOpacity(0.1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
@@ -228,7 +221,7 @@ class DashboardChartWidget extends StatelessWidget {
events[value.toInt()].title.length > 8
? '${events[value.toInt()].title.substring(0, 8)}...'
: events[value.toInt()].title,
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 8),
textAlign: TextAlign.center,
),
);
@@ -252,10 +245,10 @@ class DashboardChartWidget extends StatelessWidget {
BarChartRodData(
toY: event.currentParticipants.toDouble(),
color: event.isFull
? DashboardTheme.error
? AppColors.error
: event.isAlmostFull
? DashboardTheme.warning
: DashboardTheme.success,
? AppColors.warning
: AppColors.success,
width: 16,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
@@ -283,13 +276,13 @@ class DashboardChartWidget extends StatelessWidget {
LineChartBarData(
spots: _generateGrowthSpots(stats.monthlyGrowth),
isCurved: true,
color: stats.hasGrowth ? DashboardTheme.success : DashboardTheme.error,
color: stats.hasGrowth ? AppColors.success : AppColors.error,
barWidth: 3,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
color: (stats.hasGrowth ? DashboardTheme.success : DashboardTheme.error)
color: (stats.hasGrowth ? AppColors.success : AppColors.error)
.withOpacity(0.2),
),
),
@@ -321,12 +314,13 @@ class DashboardChartWidget extends StatelessWidget {
Widget _buildLoadingChart() {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.grey100,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: AppColors.lightBorder.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: CircularProgressIndicator(
color: DashboardTheme.royalBlue,
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen),
),
),
);
@@ -335,8 +329,8 @@ class DashboardChartWidget extends StatelessWidget {
Widget _buildErrorChart() {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: AppColors.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
@@ -344,14 +338,16 @@ class DashboardChartWidget extends StatelessWidget {
children: [
const Icon(
Icons.error_outline,
color: DashboardTheme.error,
size: 32,
color: AppColors.error,
size: 24,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Erreur de chargement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.error,
'ERREUR',
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.error,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
],
@@ -363,23 +359,24 @@ class DashboardChartWidget extends StatelessWidget {
Widget _buildEmptyChart() {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.grey50,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: AppColors.lightBorder.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.bar_chart,
color: DashboardTheme.grey400,
size: 32,
Icons.bar_chart_outlined,
color: AppColors.textSecondaryLight,
size: 24,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Aucune donnée',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey500,
'AUCUNE DONNÉE',
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
],
@@ -391,13 +388,13 @@ class DashboardChartWidget extends StatelessWidget {
IconData _getChartIcon() {
switch (chartType) {
case DashboardChartType.memberActivity:
return Icons.pie_chart;
return Icons.pie_chart_outline;
case DashboardChartType.contributionTrend:
return Icons.trending_up;
return Icons.trending_up_outlined;
case DashboardChartType.eventParticipation:
return Icons.bar_chart;
return Icons.bar_chart_outlined;
case DashboardChartType.monthlyGrowth:
return Icons.show_chart;
return Icons.show_chart_outlined;
}
}
}

View File

@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
import '../../../../../shared/widgets/mini_avatar.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../adhesions/presentation/pages/adhesions_page_wrapper.dart';
import '../../../../solidarity/presentation/pages/demandes_aide_page_wrapper.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
/// Widget des activités récentes connecté au backend
class ConnectedRecentActivities extends StatelessWidget {
@@ -21,14 +23,13 @@ class ConnectedRecentActivities extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, state) {
if (state is DashboardLoading) {
@@ -37,7 +38,7 @@ class ConnectedRecentActivities extends StatelessWidget {
final data = state is DashboardLoaded
? state.dashboardData
: (state as DashboardRefreshing).dashboardData;
return _buildActivitiesList(data.recentActivities);
return _buildActivitiesList(context, data.recentActivities);
} else if (state is DashboardError) {
return _buildErrorState(state.message);
}
@@ -52,33 +53,26 @@ class ConnectedRecentActivities extends StatelessWidget {
Widget _buildHeader() {
return Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.tealBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: const Icon(
Icons.history,
color: DashboardTheme.tealBlue,
size: 20,
),
const Icon(
Icons.history,
color: AppColors.primaryGreen,
size: 18,
),
const SizedBox(width: DashboardTheme.spacing12),
const Expanded(
const SizedBox(width: 8),
Expanded(
child: Text(
'Activités récentes',
style: DashboardTheme.titleMedium,
'ACTIVITÉS RÉCENTES',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
),
),
if (onSeeAll != null)
TextButton(
onPressed: onSeeAll,
GestureDetector(
onTap: onSeeAll,
child: Text(
'Voir tout',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.royalBlue,
fontWeight: FontWeight.w600,
'TOUT VOIR',
style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen,
fontWeight: FontWeight.bold,
),
),
),
@@ -86,7 +80,7 @@ class ConnectedRecentActivities extends StatelessWidget {
);
}
Widget _buildActivitiesList(List<RecentActivityEntity> activities) {
Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) {
if (activities.isEmpty) {
return _buildEmptyState();
}
@@ -101,79 +95,60 @@ class ConnectedRecentActivities extends StatelessWidget {
return Column(
children: [
_buildActivityItem(activity),
if (!isLast) const SizedBox(height: DashboardTheme.spacing12),
_buildActivityItem(context, activity),
if (!isLast) const SizedBox(height: 12),
],
);
}).toList(),
);
}
Widget _buildActivityItem(RecentActivityEntity activity) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar ou icône
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _getActivityColor(activity.type).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
Widget _buildActivityItem(BuildContext context, RecentActivityEntity activity) {
return InkWell(
onTap: () => _navigateForActivity(context, activity),
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MiniAvatar(
fallbackText: activity.userName.isNotEmpty ? activity.userName[0].toUpperCase() : '?',
imageUrl: activity.userAvatar,
size: 32,
),
child: activity.userAvatar != null
? ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.network(
activity.userAvatar!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Icon(
_getActivityIcon(activity.type),
color: _getActivityColor(activity.type),
size: 20,
),
),
)
: Icon(
_getActivityIcon(activity.type),
color: _getActivityColor(activity.type),
size: 20,
),
),
const SizedBox(width: DashboardTheme.spacing12),
// Contenu
Expanded(
const SizedBox(width: 12),
// Contenu
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: DashboardTheme.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 2,
style: AppTypography.actionText.copyWith(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: DashboardTheme.spacing4),
Text(
activity.description,
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 2),
Row(
children: [
Text(
activity.userName,
style: DashboardTheme.bodySmall.copyWith(
fontWeight: FontWeight.w500,
color: DashboardTheme.royalBlue,
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.primaryGreen,
fontSize: 9,
),
),
Text(
'${activity.timeAgo}',
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 9),
),
],
),
@@ -182,15 +157,14 @@ class ConnectedRecentActivities extends StatelessWidget {
),
// Action button si disponible
if (activity.hasAction)
IconButton(
onPressed: () => _navigateForActivity(context, activity),
icon: const Icon(
Icons.arrow_forward_ios,
size: 16,
color: DashboardTheme.grey400,
),
const Icon(
Icons.chevron_right,
size: 14,
color: AppColors.textSecondaryLight,
),
],
],
),
),
);
}
@@ -220,7 +194,7 @@ class ConnectedRecentActivities extends StatelessWidget {
children: List.generate(3, (index) => Column(
children: [
_buildLoadingItem(),
if (index < 2) const SizedBox(height: DashboardTheme.spacing12),
if (index < 2) const SizedBox(height: 12),
],
)),
);
@@ -233,11 +207,11 @@ class ConnectedRecentActivities extends StatelessWidget {
width: 40,
height: 40,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
color: AppColors.lightBorder,
borderRadius: BorderRadius.circular(20),
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -246,25 +220,25 @@ class ConnectedRecentActivities extends StatelessWidget {
height: 16,
width: double.infinity,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
color: AppColors.lightBorder,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 4),
Container(
height: 12,
width: 200,
decoration: BoxDecoration(
color: DashboardTheme.grey100,
color: AppColors.lightBorder.withOpacity(0.5),
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 4),
Container(
height: 12,
width: 120,
decoration: BoxDecoration(
color: DashboardTheme.grey100,
color: AppColors.lightBorder.withOpacity(0.5),
borderRadius: BorderRadius.circular(4),
),
),
@@ -279,24 +253,9 @@ class ConnectedRecentActivities extends StatelessWidget {
return Center(
child: Column(
children: [
const Icon(
Icons.error_outline,
color: DashboardTheme.error,
size: 48,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
'Erreur de chargement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.error,
),
),
const SizedBox(height: DashboardTheme.spacing4),
Text(
message,
style: DashboardTheme.bodySmall,
textAlign: TextAlign.center,
),
const Icon(Icons.error_outline, color: AppColors.error, size: 32),
const SizedBox(height: 8),
Text(message, style: AppTypography.subtitleSmall.copyWith(color: AppColors.error)),
],
),
);
@@ -306,24 +265,10 @@ class ConnectedRecentActivities extends StatelessWidget {
return Center(
child: Column(
children: [
const Icon(
Icons.history,
color: DashboardTheme.grey400,
size: 48,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
'Aucune activité récente',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey500,
),
),
const SizedBox(height: DashboardTheme.spacing4),
const Text(
'Les activités apparaîtront ici',
style: DashboardTheme.bodySmall,
textAlign: TextAlign.center,
),
const Icon(Icons.history, color: AppColors.textSecondaryLight, size: 32),
const SizedBox(height: 8),
const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall),
Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
],
),
);
@@ -349,17 +294,17 @@ class ConnectedRecentActivities extends StatelessWidget {
Color _getActivityColor(String type) {
switch (type.toLowerCase()) {
case 'member':
return DashboardTheme.success;
return AppColors.success;
case 'event':
return DashboardTheme.info;
return AppColors.info;
case 'contribution':
return DashboardTheme.tealBlue;
return AppColors.brandGreen;
case 'organization':
return DashboardTheme.royalBlue;
return AppColors.primaryGreen;
case 'system':
return DashboardTheme.warning;
return AppColors.warning;
default:
return DashboardTheme.grey500;
return AppColors.textSecondaryLight;
}
}
}

View File

@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../domain/entities/dashboard_entity.dart';
/// Widget de carte de statistiques connecté au backend
class ConnectedStatsCard extends StatelessWidget {
@@ -45,157 +46,85 @@ class ConnectedStatsCard extends StatelessWidget {
Widget _buildDataCard(DashboardStatsEntity stats) {
final value = valueExtractor(stats);
final subtitle = subtitleExtractor?.call(stats);
final color = customColor ?? DashboardTheme.royalBlue;
final color = customColor ?? AppColors.primaryGreen;
return GestureDetector(
return CoreCard(
onTap: onTap,
child: Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const SizedBox(width: DashboardTheme.spacing12),
Expanded(
child: Text(
title,
style: DashboardTheme.titleSmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: DashboardTheme.spacing16),
Text(
value,
style: DashboardTheme.metricLarge.copyWith(color: color),
),
if (subtitle != null) ...[
const SizedBox(height: DashboardTheme.spacing4),
Text(
subtitle,
style: DashboardTheme.bodySmall,
),
],
],
),
),
);
}
Widget _buildLoadingCard() {
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 40,
height: 40,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 20,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Expanded(
child: Container(
height: 16,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(4),
child: Text(
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
fontSize: 10,
letterSpacing: 1.1,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: DashboardTheme.spacing16),
Container(
height: 32,
width: 80,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(4),
const SizedBox(height: 12),
Text(
value,
style: AppTypography.headerSmall.copyWith(
color: color,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: DashboardTheme.spacing4),
Container(
height: 12,
width: 120,
decoration: BoxDecoration(
color: DashboardTheme.grey100,
borderRadius: BorderRadius.circular(4),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle,
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
),
),
],
],
),
);
}
Widget _buildLoadingCard() {
return const CoreCard(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// On peut utiliser un Shimmer ici si disponible
CircularProgressIndicator(strokeWidth: 2),
],
),
);
}
Widget _buildErrorCard(String message) {
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: const Icon(
Icons.error_outline,
color: DashboardTheme.error,
size: 24,
),
),
const SizedBox(width: DashboardTheme.spacing12),
Expanded(
child: Text(
title,
style: DashboardTheme.titleSmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: DashboardTheme.spacing16),
Text(
'--',
style: DashboardTheme.metricLarge.copyWith(
color: DashboardTheme.grey400,
),
),
const SizedBox(height: DashboardTheme.spacing4),
Text(
message,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.error,
),
),
const Icon(Icons.error_outline, color: AppColors.error, size: 20),
const SizedBox(height: 8),
Text(message, style: AppTypography.subtitleSmall.copyWith(color: AppColors.error)),
],
),
);

View File

@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
/// Widget des événements à venir connecté au backend
class ConnectedUpcomingEvents extends StatelessWidget {
@@ -17,23 +18,22 @@ class ConnectedUpcomingEvents extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, state) {
builder: (ctx, state) {
if (state is DashboardLoading) {
return _buildLoadingList();
} else if (state is DashboardLoaded || state is DashboardRefreshing) {
final data = state is DashboardLoaded
? state.dashboardData
: (state as DashboardRefreshing).dashboardData;
return _buildEventsList(data.upcomingEvents);
return _buildEventsList(context, data.upcomingEvents);
} else if (state is DashboardError) {
return _buildErrorState(state.message);
}
@@ -48,33 +48,26 @@ class ConnectedUpcomingEvents extends StatelessWidget {
Widget _buildHeader() {
return Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.royalBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: const Icon(
Icons.event,
color: DashboardTheme.royalBlue,
size: 20,
),
const Icon(
Icons.event_outlined,
color: AppColors.primaryGreen,
size: 18,
),
const SizedBox(width: DashboardTheme.spacing12),
const Expanded(
const SizedBox(width: 8),
Expanded(
child: Text(
'Événements à venir',
style: DashboardTheme.titleMedium,
'ÉVÉNEMENTS À VENIR',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
),
),
if (onSeeAll != null)
TextButton(
onPressed: onSeeAll,
GestureDetector(
onTap: onSeeAll,
child: Text(
'Voir tout',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.royalBlue,
fontWeight: FontWeight.w600,
'TOUT VOIR',
style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen,
fontWeight: FontWeight.bold,
),
),
),
@@ -82,7 +75,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
);
}
Widget _buildEventsList(List<UpcomingEventEntity> events) {
Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) {
if (events.isEmpty) {
return _buildEmptyState();
}
@@ -97,86 +90,62 @@ class ConnectedUpcomingEvents extends StatelessWidget {
return Column(
children: [
_buildEventCard(event),
if (!isLast) const SizedBox(height: DashboardTheme.spacing12),
_buildEventCard(context, event),
if (!isLast) const SizedBox(height: 12),
],
);
}).toList(),
);
}
Widget _buildEventCard(UpcomingEventEntity event) {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.grey50,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
border: Border.all(
color: event.isToday
? DashboardTheme.success
: event.isTomorrow
? DashboardTheme.warning
: DashboardTheme.grey200,
width: event.isToday || event.isTomorrow ? 2 : 1,
),
),
padding: const EdgeInsets.all(DashboardTheme.spacing12),
Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) {
final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primaryGreen);
return CoreCard(
backgroundColor: Theme.of(context).cardColor,
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
// Image ou icône
Container(
width: 50,
height: 50,
width: 44,
height: 44,
decoration: BoxDecoration(
color: DashboardTheme.royalBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: event.imageUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(8),
child: Image.network(
event.imageUrl!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.event,
color: DashboardTheme.royalBlue,
size: 24,
),
errorBuilder: (context, error, stackTrace) => Icon(Icons.event_outlined, color: statusColor, size: 20),
),
)
: const Icon(
Icons.event,
color: DashboardTheme.royalBlue,
size: 24,
),
: Icon(Icons.event_outlined, color: statusColor, size: 20),
),
const SizedBox(width: DashboardTheme.spacing12),
// Contenu principal
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: DashboardTheme.titleSmall,
maxLines: 2,
style: AppTypography.actionText.copyWith(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: DashboardTheme.spacing4),
Row(
children: [
const Icon(
Icons.location_on,
size: 14,
color: DashboardTheme.grey500,
),
const SizedBox(width: DashboardTheme.spacing4),
const Icon(Icons.location_on_outlined, size: 10, color: AppColors.textSecondaryLight),
const SizedBox(width: 4),
Expanded(
child: Text(
event.location,
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 9),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -186,99 +155,47 @@ class ConnectedUpcomingEvents extends StatelessWidget {
],
),
),
// Badge de temps
Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing8,
vertical: DashboardTheme.spacing4,
),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: event.isToday
? DashboardTheme.success.withOpacity(0.1)
: event.isTomorrow
? DashboardTheme.warning.withOpacity(0.1)
: DashboardTheme.royalBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
event.daysUntilEvent,
style: DashboardTheme.bodySmall.copyWith(
color: event.isToday
? DashboardTheme.success
: event.isTomorrow
? DashboardTheme.warning
: DashboardTheme.royalBlue,
fontWeight: FontWeight.w600,
),
event.daysUntilEvent.toUpperCase(),
style: AppTypography.badgeText.copyWith(color: statusColor, fontSize: 8, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: DashboardTheme.spacing12),
// Barre de progression des participants
Row(
const SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Participants',
style: DashboardTheme.bodySmall,
),
Text(
'${event.currentParticipants}/${event.maxParticipants}',
style: DashboardTheme.bodySmall.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: DashboardTheme.spacing4),
LinearProgressIndicator(
value: event.fillPercentage,
backgroundColor: DashboardTheme.grey200,
valueColor: AlwaysStoppedAnimation<Color>(
event.isFull
? DashboardTheme.error
: event.isAlmostFull
? DashboardTheme.warning
: DashboardTheme.success,
),
),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('PARTICIPANTS', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
Text(
'${event.currentParticipants}/${event.maxParticipants}',
style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 4),
ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: event.fillPercentage,
minHeight: 4,
backgroundColor: AppColors.lightBorder,
valueColor: AlwaysStoppedAnimation<Color>(
event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success),
),
),
),
],
),
// Tags
if (event.tags.isNotEmpty) ...[
const SizedBox(height: DashboardTheme.spacing8),
Wrap(
spacing: DashboardTheme.spacing4,
runSpacing: DashboardTheme.spacing4,
children: event.tags.take(3).map((tag) => Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing8,
vertical: DashboardTheme.spacing4,
),
decoration: BoxDecoration(
color: DashboardTheme.tealBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
tag,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.tealBlue,
fontWeight: FontWeight.w500,
),
),
)).toList(),
),
],
],
),
);
@@ -289,78 +206,15 @@ class ConnectedUpcomingEvents extends StatelessWidget {
children: List.generate(2, (index) => Column(
children: [
_buildLoadingCard(),
if (index < 1) const SizedBox(height: DashboardTheme.spacing12),
if (index < 1) const SizedBox(height: 12),
],
)),
);
}
Widget _buildLoadingCard() {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.grey50,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
border: Border.all(color: DashboardTheme.grey200),
),
padding: const EdgeInsets.all(DashboardTheme.spacing12),
child: Column(
children: [
Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
),
const SizedBox(width: DashboardTheme.spacing12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 16,
width: double.infinity,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: DashboardTheme.spacing4),
Container(
height: 12,
width: 120,
decoration: BoxDecoration(
color: DashboardTheme.grey100,
borderRadius: BorderRadius.circular(4),
),
),
],
),
),
Container(
width: 60,
height: 24,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(12),
),
),
],
),
const SizedBox(height: DashboardTheme.spacing12),
Container(
height: 4,
width: double.infinity,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(2),
),
),
],
),
return const CoreCard(
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
}
@@ -368,24 +222,9 @@ class ConnectedUpcomingEvents extends StatelessWidget {
return Center(
child: Column(
children: [
const Icon(
Icons.error_outline,
color: DashboardTheme.error,
size: 48,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
'Erreur de chargement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.error,
),
),
const SizedBox(height: DashboardTheme.spacing4),
Text(
message,
style: DashboardTheme.bodySmall,
textAlign: TextAlign.center,
),
const Icon(Icons.error_outline, color: AppColors.error, size: 32),
const SizedBox(height: 8),
Text(message, style: AppTypography.subtitleSmall.copyWith(color: AppColors.error)),
],
),
);
@@ -395,24 +234,10 @@ class ConnectedUpcomingEvents extends StatelessWidget {
return Center(
child: Column(
children: [
const Icon(
Icons.event_busy,
color: DashboardTheme.grey400,
size: 48,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
'Aucun événement à venir',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey500,
),
),
const SizedBox(height: DashboardTheme.spacing4),
const Text(
'Les événements apparaîtront ici',
style: DashboardTheme.bodySmall,
textAlign: TextAlign.center,
),
const Icon(Icons.event_outlined, color: AppColors.textSecondaryLight, size: 32),
const SizedBox(height: 8),
const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall),
Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
],
),
);

View File

@@ -3,189 +3,233 @@
library dashboard_drawer;
import 'package:flutter/material.dart';
import '../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../shared/design_system/tokens/spacing_tokens.dart';
import '../../../../shared/design_system/tokens/typography_tokens.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
/// Modèle de données pour un élément de menu
class DrawerMenuItem {
/// Icône de l'élément de menu
final IconData icon;
/// Titre de l'élément de menu
final String title;
/// Callback lors du tap sur l'élément
final VoidCallback? onTap;
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/widgets/core_card.dart';
import '../../../../shared/widgets/mini_avatar.dart';
/// Constructeur du modèle d'élément de menu
const DrawerMenuItem({
required this.icon,
required this.title,
this.onTap,
});
}
import '../../../authentication/presentation/bloc/auth_bloc.dart';
/// Widget de menu latéral
///
/// Affiche la navigation principale avec :
/// - Header avec profil utilisateur
/// - Menu de navigation structuré
/// - Actions secondaires
/// - Design Material avec gradient
import '../../../profile/presentation/pages/profile_page_wrapper.dart';
import '../../../notifications/presentation/pages/notifications_page_wrapper.dart';
import '../../../help/presentation/pages/help_support_page.dart';
import '../../../about/presentation/pages/about_page.dart';
/// Widget de menu latéral (Drawer / Hamburger)
///
/// Accessible via le bouton hamburger de l'AppBar.
/// Contient uniquement les menus « Mon Espace » :
/// - Mon Profil
/// - Notifications
/// - Aide & Support
/// - À propos
/// - Déconnexion
class DashboardDrawer extends StatelessWidget {
/// Callback pour les actions de navigation
/// Callback pour les actions de navigation nommée (optionnel, non utilisé en interne)
final Function(String route)? onNavigate;
/// Callback pour la déconnexion
final VoidCallback? onLogout;
/// Constructeur du menu latéral
const DashboardDrawer({
super.key,
this.onNavigate,
this.onLogout,
});
/// Génère la liste des éléments de menu principaux
List<DrawerMenuItem> _getMainMenuItems() {
return [
DrawerMenuItem(
icon: Icons.dashboard,
title: 'Dashboard',
onTap: () => onNavigate?.call('/dashboard'),
),
DrawerMenuItem(
icon: Icons.people,
title: 'Membres',
onTap: () => onNavigate?.call('/members'),
),
DrawerMenuItem(
icon: Icons.account_balance_wallet,
title: 'Cotisations',
onTap: () => onNavigate?.call('/cotisations'),
),
DrawerMenuItem(
icon: Icons.event,
title: 'Événements',
onTap: () => onNavigate?.call('/events'),
),
DrawerMenuItem(
icon: Icons.favorite,
title: 'Solidarité',
onTap: () => onNavigate?.call('/solidarity'),
),
];
}
/// Génère la liste des éléments de menu secondaires
List<DrawerMenuItem> _getSecondaryMenuItems() {
return [
DrawerMenuItem(
icon: Icons.analytics,
title: 'Rapports',
onTap: () => onNavigate?.call('/reports'),
),
DrawerMenuItem(
icon: Icons.settings,
title: 'Paramètres',
onTap: () => onNavigate?.call('/settings'),
),
DrawerMenuItem(
icon: Icons.help,
title: 'Aide',
onTap: () => onNavigate?.call('/help'),
),
];
}
@override
Widget build(BuildContext context) {
final mainItems = _getMainMenuItems();
final secondaryItems = _getSecondaryMenuItems();
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
if (authState is! AuthAuthenticated) {
return const Drawer();
}
final state = authState;
return Drawer(
backgroundColor: ColorTokens.background,
child: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── En-tête utilisateur (même style que MorePage) ──────────────
_buildUserProfile(state),
const SizedBox(height: SpacingTokens.md),
// ── Section Mon Espace ─────────────────────────────────────────
_buildSectionTitle('Mon Espace'),
_buildOptionTile(
context: context,
icon: Icons.person,
title: 'Mon Profil',
subtitle: 'Modifier mes informations',
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const ProfilePageWrapper()),
),
),
_buildOptionTile(
context: context,
icon: Icons.notifications,
title: 'Notifications',
subtitle: 'Gérer les notifications',
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const NotificationsPageWrapper()),
),
),
_buildOptionTile(
context: context,
icon: Icons.help,
title: 'Aide & Support',
subtitle: 'Documentation et support',
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const HelpSupportPage()),
),
),
_buildOptionTile(
context: context,
icon: Icons.info,
title: 'À propos',
subtitle: 'Version et informations',
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const AboutPage()),
),
),
const SizedBox(height: SpacingTokens.md),
// ── Déconnexion ───────────────────────────────────────────────
_buildOptionTile(
context: context,
icon: Icons.logout,
title: 'Déconnexion',
subtitle: 'Se déconnecter de l\'application',
color: ColorTokens.error,
onTap: () {
Navigator.pop(context);
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
),
],
),
),
),
);
},
);
}
// ── Profil utilisateur (idem MorePage._buildUserProfile) ──────────────────
Widget _buildUserProfile(AuthAuthenticated state) {
return CoreCard(
child: Row(
children: [
_buildDrawerHeader(),
...mainItems.map((item) => _buildMenuItem(item)),
const Divider(),
...secondaryItems.map((item) => _buildMenuItem(item)),
const Divider(),
_buildLogoutItem(),
MiniAvatar(
fallbackText:
state.user.firstName.isNotEmpty ? state.user.firstName[0].toUpperCase() : 'U',
size: 40,
imageUrl: state.user.avatar,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${state.user.firstName} ${state.user.lastName}',
style: AppTypography.actionText,
),
Text(
state.effectiveRole.displayName.toUpperCase(),
style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen,
fontWeight: FontWeight.bold,
),
),
Text(
state.user.email,
style: AppTypography.subtitleSmall,
),
],
),
),
],
),
);
}
/// Construit l'en-tête du drawer avec profil utilisateur
Widget _buildDrawerHeader() {
return DrawerHeader(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [ColorTokens.primary, ColorTokens.secondary],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
// ── Titre de section (idem MorePage._buildSectionTitle) ───────────────────
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(top: 24, bottom: 8, left: 4),
child: Text(
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 1.1,
color: AppColors.textSecondaryLight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
);
}
// ── Tuile d'option (idem MorePage._buildOptionTile) ───────────────────────
Widget _buildOptionTile({
required BuildContext context,
required IconData icon,
required String title,
required String subtitle,
required VoidCallback onTap,
Color? color,
}) {
final effectiveColor = color ?? AppColors.primaryGreen;
return CoreCard(
margin: const EdgeInsets.only(bottom: 8),
onTap: onTap,
child: Row(
children: [
const CircleAvatar(
radius: 30,
backgroundColor: Colors.white,
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: effectiveColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.person,
size: 35,
color: ColorTokens.primary,
icon,
color: effectiveColor,
size: 20,
),
),
const SizedBox(height: SpacingTokens.md),
Text(
'Utilisateur UnionFlow',
style: TypographyTokens.titleMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTypography.actionText.copyWith(
color: color ?? AppColors.textPrimaryLight,
),
),
Text(
subtitle,
style: AppTypography.subtitleSmall,
),
],
),
),
Text(
'admin@unionflow.dev',
style: TypographyTokens.bodySmall.copyWith(
color: Colors.white.withOpacity(0.8),
),
Icon(
Icons.chevron_right,
color: AppColors.textSecondaryLight,
size: 16,
),
],
),
);
}
/// Construit un élément de menu
Widget _buildMenuItem(DrawerMenuItem item) {
return ListTile(
leading: Icon(item.icon),
title: Text(
item.title,
style: TypographyTokens.bodyMedium,
),
onTap: item.onTap,
);
}
/// Construit l'élément de déconnexion
Widget _buildLogoutItem() {
return ListTile(
leading: const Icon(
Icons.logout,
color: ColorTokens.error,
),
title: Text(
'Déconnexion',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.error,
),
),
onTap: onLogout,
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../shared/design_system/dashboard_theme.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/widgets/core_card.dart';
/// Widget de statistique simple pour les dashboards de rôle
class DashboardStat extends StatelessWidget {
@@ -18,13 +19,8 @@ class DashboardStat extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.cardShadow,
),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -32,22 +28,27 @@ class DashboardStat extends StatelessWidget {
children: [
Icon(
icon,
color: color ?? DashboardTheme.royalBlue,
size: 24,
color: color ?? AppColors.primaryGreen,
size: 20,
),
const Spacer(),
Text(
value,
style: DashboardTheme.titleLarge.copyWith(
color: color ?? DashboardTheme.royalBlue,
style: AppTypography.headerSmall.copyWith(
color: color ?? AppColors.primaryGreen,
fontSize: 18,
),
),
],
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
title,
style: DashboardTheme.bodyMedium,
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
fontSize: 10,
letterSpacing: 1.1,
),
),
],
),
@@ -72,9 +73,9 @@ class DashboardStatsGrid extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: DashboardTheme.spacing12,
crossAxisSpacing: DashboardTheme.spacing12,
childAspectRatio: 1.2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.3,
children: stats,
);
}
@@ -95,9 +96,9 @@ class DashboardQuickActionsGrid extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: DashboardTheme.spacing12,
crossAxisSpacing: DashboardTheme.spacing12,
childAspectRatio: 1.5,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.4,
children: children,
);
}
@@ -120,37 +121,34 @@ class DashboardQuickAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
return CoreCard(
onTap: onTap,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
child: Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.cardShadow,
border: Border.all(
color: (color ?? DashboardTheme.royalBlue).withOpacity(0.2),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: (color ?? AppColors.primaryGreen).withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: color ?? DashboardTheme.royalBlue,
size: 32,
color: color ?? AppColors.primaryGreen,
size: 24,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
title,
style: DashboardTheme.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
title,
style: AppTypography.actionText.copyWith(
fontSize: 12,
fontWeight: FontWeight.w600,
),
],
),
textAlign: TextAlign.center,
),
],
),
);
}
@@ -167,21 +165,19 @@ class DashboardRecentActivitySection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.cardShadow,
),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Activités récentes',
style: DashboardTheme.titleMedium,
Text(
'ACTIVITÉS RÉCENTES',
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 1.1,
),
),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
...children,
],
),
@@ -209,43 +205,45 @@ class DashboardActivity extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: DashboardTheme.spacing12),
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: (color ?? DashboardTheme.royalBlue).withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: (color ?? AppColors.primaryGreen).withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Icon(
icon,
color: color ?? DashboardTheme.royalBlue,
size: 16,
color: color ?? AppColors.primaryGreen,
size: 14,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: DashboardTheme.bodyMedium.copyWith(
style: AppTypography.actionText.copyWith(
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
Text(
subtitle,
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
),
],
),
),
Text(
time,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey500,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
fontSize: 9,
),
),
],

View File

@@ -1,9 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:async';
import '../../../domain/entities/dashboard_entity.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
/// Widget de métriques en temps réel avec animations
class RealTimeMetricsWidget extends StatefulWidget {
@@ -81,13 +82,27 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
@override
Widget build(BuildContext context) {
return Container(
decoration: DashboardTheme.gradientCardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.brandGreen, AppColors.primaryGreen],
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColors.primaryGreen.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: DashboardTheme.spacing20),
const SizedBox(height: 20),
BlocConsumer<DashboardBloc, DashboardState>(
listener: (context, state) {
if (state is DashboardLoaded) {
@@ -122,37 +137,39 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
return Transform.scale(
scale: _pulseAnimation.value,
child: Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: DashboardTheme.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
),
child: const Icon(
Icons.speed,
color: DashboardTheme.white,
size: 24,
Icons.speed_outlined,
color: Colors.white,
size: 20,
),
),
);
},
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Métriques Temps Réel',
style: DashboardTheme.titleMedium.copyWith(
color: DashboardTheme.white,
'MÉTRIQUES TEMPS RÉEL',
style: AppTypography.actionText.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 2),
Text(
'Mise à jour automatique',
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white.withOpacity(0.8),
'Mise à jour automatique (5 min)',
style: AppTypography.subtitleSmall.copyWith(
color: Colors.white.withOpacity(0.8),
fontSize: 10,
),
),
],
@@ -172,7 +189,7 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(DashboardTheme.white),
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
);
}
@@ -185,15 +202,15 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
));
},
child: Container(
padding: const EdgeInsets.all(DashboardTheme.spacing4),
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: DashboardTheme.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
),
child: const Icon(
Icons.refresh,
color: DashboardTheme.white,
size: 16,
Icons.refresh_outlined,
color: Colors.white,
size: 14,
),
),
);
@@ -211,27 +228,27 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
children: [
Expanded(
child: _buildMetricItem(
'Membres Actifs',
'MEMBRES ACTIFS',
(data.stats.activeMembers * _countAnimation.value).round(),
data.stats.totalMembers,
Icons.people,
DashboardTheme.success,
Icons.people_outline,
AppColors.success,
),
),
const SizedBox(width: DashboardTheme.spacing16),
const SizedBox(width: 16),
Expanded(
child: _buildMetricItem(
'Engagement',
((data.stats.engagementRate * 100) * _countAnimation.value).round(),
100,
Icons.favorite,
DashboardTheme.warning,
AppColors.warning,
suffix: '%',
),
),
],
),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
Row(
children: [
Expanded(
@@ -240,17 +257,17 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
(data.stats.upcomingEvents * _countAnimation.value).round(),
data.stats.totalEvents,
Icons.event,
DashboardTheme.info,
AppColors.info,
),
),
const SizedBox(width: DashboardTheme.spacing16),
const SizedBox(width: 16),
Expanded(
child: _buildMetricItem(
'Croissance',
(data.stats.monthlyGrowth * _countAnimation.value),
null,
Icons.trending_up,
data.stats.hasGrowth ? DashboardTheme.success : DashboardTheme.error,
data.stats.hasGrowth ? AppColors.success : AppColors.error,
suffix: '%',
isDecimal: true,
),
@@ -280,12 +297,12 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
}
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: DashboardTheme.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: DashboardTheme.white.withOpacity(0.2),
color: Colors.white.withOpacity(0.2),
),
),
child: Column(
@@ -296,34 +313,36 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
Icon(
icon,
color: color,
size: 20,
size: 16,
),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white.withOpacity(0.8),
label.toUpperCase(),
style: AppTypography.badgeText.copyWith(
color: Colors.white.withOpacity(0.8),
fontSize: 8,
),
),
),
],
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
displayValue,
style: DashboardTheme.titleLarge.copyWith(
color: DashboardTheme.white,
style: AppTypography.headerSmall.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 24,
fontSize: 20,
),
),
if (maxValue != null) ...[
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 2),
Text(
'sur $maxValue',
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white.withOpacity(0.6),
style: AppTypography.subtitleSmall.copyWith(
color: Colors.white.withOpacity(0.6),
fontSize: 8,
),
),
],
@@ -338,15 +357,15 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
Row(
children: [
Expanded(child: _buildLoadingMetricItem()),
const SizedBox(width: DashboardTheme.spacing16),
const SizedBox(width: 16),
Expanded(child: _buildLoadingMetricItem()),
],
),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
Row(
children: [
Expanded(child: _buildLoadingMetricItem()),
const SizedBox(width: DashboardTheme.spacing16),
const SizedBox(width: 16),
Expanded(child: _buildLoadingMetricItem()),
],
),
@@ -357,15 +376,15 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
Widget _buildLoadingMetricItem() {
return Container(
height: 100,
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: DashboardTheme.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(DashboardTheme.white),
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
);
@@ -375,8 +394,8 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
return Container(
height: 200,
decoration: BoxDecoration(
color: DashboardTheme.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: AppColors.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
@@ -384,14 +403,14 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
children: [
const Icon(
Icons.error_outline,
color: DashboardTheme.error,
color: AppColors.error,
size: 32,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Erreur de chargement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.error,
style: AppTypography.bodyTextSmall.copyWith(
color: AppColors.error,
),
),
],
@@ -404,8 +423,8 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
return Container(
height: 200,
decoration: BoxDecoration(
color: DashboardTheme.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Column(
@@ -413,14 +432,14 @@ class _RealTimeMetricsWidgetState extends State<RealTimeMetricsWidget>
children: [
Icon(
Icons.speed,
color: DashboardTheme.white.withOpacity(0.5),
color: Colors.white.withOpacity(0.5),
size: 32,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Aucune donnée',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.white.withOpacity(0.7),
style: AppTypography.bodyTextSmall.copyWith(
color: Colors.white.withOpacity(0.7),
),
),
],

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'dart:async';
import '../../../../../shared/design_system/dashboard_theme.dart';
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
import '../../../data/services/dashboard_performance_monitor.dart';
/// Widget de monitoring des performances en temps réel
@@ -127,13 +128,9 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
return _buildLoadingWidget();
}
return Container(
margin: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.subtleShadow,
),
return CoreCard(
margin: const EdgeInsets.all(8),
padding: EdgeInsets.zero,
child: Column(
children: [
_buildHeader(),
@@ -151,27 +148,23 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
}
Widget _buildLoadingWidget() {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.subtleShadow,
),
child: const Row(
return CoreCard(
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(16),
child: Row(
children: [
SizedBox(
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(DashboardTheme.royalBlue),
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen),
),
),
SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Text(
'Initialisation du monitoring...',
style: DashboardTheme.bodyMedium,
style: AppTypography.bodyTextSmall,
),
],
),
@@ -185,9 +178,8 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
_isExpanded = !_isExpanded;
});
},
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
child: Padding(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
child: Row(
children: [
AnimatedBuilder(
@@ -213,18 +205,24 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
);
},
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Performances Système',
style: DashboardTheme.titleSmall,
'PERFORMANCES SYSTÈME',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
letterSpacing: 1.1,
color: AppColors.textPrimaryLight,
),
),
),
_buildQuickMetrics(),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
Icon(
_isExpanded ? Icons.expand_less : Icons.expand_more,
color: DashboardTheme.grey600,
color: AppColors.textSecondaryLight,
size: 20,
),
],
),
@@ -241,13 +239,13 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
'${_currentMetrics!.memoryUsage.toStringAsFixed(0)}MB',
_getMetricColor(_currentMetrics!.memoryUsage, 400, 600),
),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
_buildQuickMetric(
'CPU',
'${_currentMetrics!.cpuUsage.toStringAsFixed(0)}%',
_getMetricColor(_currentMetrics!.cpuUsage, 50, 80),
),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
_buildQuickMetric(
'NET',
'${_currentMetrics!.networkLatency}ms',
@@ -264,8 +262,8 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
Text(
label,
style: const TextStyle(
fontSize: 10,
color: DashboardTheme.grey600,
fontSize: 9,
color: AppColors.textSecondaryLight,
fontWeight: FontWeight.w500,
),
),
@@ -283,7 +281,7 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
Widget _buildDetailedMetrics() {
return Padding(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildMetricRow(
@@ -293,37 +291,37 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
_getMetricColor(_currentMetrics!.memoryUsage, 400, 600),
Icons.memory,
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: 12),
_buildMetricRow(
'Processeur',
'${_currentMetrics!.cpuUsage.toStringAsFixed(1)}%',
_currentMetrics!.cpuUsage / 100,
_getMetricColor(_currentMetrics!.cpuUsage, 50, 80),
Icons.speed,
Icons.speed_outlined,
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: 12),
_buildMetricRow(
'Réseau',
'${_currentMetrics!.networkLatency} ms',
(_currentMetrics!.networkLatency / 2000).clamp(0.0, 1.0),
_getMetricColor(_currentMetrics!.networkLatency.toDouble(), 200, 1000),
Icons.wifi,
Icons.wifi_outlined,
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: 12),
_buildMetricRow(
'Images/sec',
'${_currentMetrics!.frameRate.toStringAsFixed(1)} fps',
_currentMetrics!.frameRate / 60,
_getMetricColor(60 - _currentMetrics!.frameRate, 10, 30), // Inversé car plus c'est haut, mieux c'est
Icons.videocam,
Icons.videocam_outlined,
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: 12),
_buildMetricRow(
'Batterie',
'${_currentMetrics!.batteryLevel.toStringAsFixed(0)}%',
_currentMetrics!.batteryLevel / 100,
_getBatteryColor(_currentMetrics!.batteryLevel),
Icons.battery_std,
Icons.battery_std_outlined,
),
],
),
@@ -340,23 +338,27 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
return Row(
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
Expanded(
flex: 2,
child: Text(
label,
style: DashboardTheme.bodySmall,
style: AppTypography.subtitleSmall.copyWith(fontSize: 11),
),
),
Expanded(
flex: 3,
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
backgroundColor: DashboardTheme.grey200,
valueColor: AlwaysStoppedAnimation<Color>(color),
child: ClipRRect(
borderRadius: BorderRadius.circular(2),
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
backgroundColor: AppColors.lightBorder,
valueColor: AlwaysStoppedAnimation<Color>(color),
minHeight: 4,
),
),
),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
SizedBox(
width: 60,
child: Text(
@@ -375,15 +377,15 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
Widget _buildAlertsSection() {
return Padding(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Alertes Récentes',
style: DashboardTheme.titleSmall,
Text(
'ALERTES RÉCENTES',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, fontSize: 10),
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
..._recentAlerts.take(3).map((alert) => _buildAlertItem(alert)),
],
),
@@ -402,13 +404,13 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
size: 16,
color: color,
),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
Expanded(
child: Text(
alert.message,
style: const TextStyle(
fontSize: 12,
color: DashboardTheme.grey700,
fontSize: 11,
color: AppColors.textPrimaryLight,
),
),
),
@@ -416,7 +418,7 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
_formatTime(alert.timestamp),
style: const TextStyle(
fontSize: 10,
color: DashboardTheme.grey500,
color: AppColors.textSecondaryLight,
),
),
],
@@ -425,7 +427,7 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
}
Color _getOverallHealthColor() {
if (_currentMetrics == null) return DashboardTheme.grey400;
if (_currentMetrics == null) return AppColors.textSecondaryLight;
final metrics = _currentMetrics!;
@@ -438,36 +440,36 @@ class _PerformanceMonitorWidgetState extends State<PerformanceMonitorWidget>
switch (issues) {
case 0:
return DashboardTheme.success;
return AppColors.success;
case 1:
return DashboardTheme.warning;
return AppColors.warning;
default:
return DashboardTheme.error;
return AppColors.error;
}
}
Color _getMetricColor(double value, double warningThreshold, double errorThreshold) {
if (value >= errorThreshold) return DashboardTheme.error;
if (value >= warningThreshold) return DashboardTheme.warning;
return DashboardTheme.success;
if (value >= errorThreshold) return AppColors.error;
if (value >= warningThreshold) return AppColors.warning;
return AppColors.success;
}
Color _getBatteryColor(double batteryLevel) {
if (batteryLevel <= 20) return DashboardTheme.error;
if (batteryLevel <= 50) return DashboardTheme.warning;
return DashboardTheme.success;
if (batteryLevel <= 20) return AppColors.error;
if (batteryLevel <= 50) return AppColors.warning;
return AppColors.success;
}
Color _getAlertColor(AlertSeverity severity) {
switch (severity) {
case AlertSeverity.info:
return DashboardTheme.info;
return AppColors.info;
case AlertSeverity.warning:
return DashboardTheme.warning;
return AppColors.warning;
case AlertSeverity.error:
return DashboardTheme.error;
return AppColors.error;
case AlertSeverity.critical:
return DashboardTheme.error;
return AppColors.error;
}
}

View File

@@ -1,7 +1,13 @@
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../pages/connected_dashboard_page.dart';
import '../../pages/advanced_dashboard_page.dart';
import '../../../../settings/presentation/pages/language_settings_page.dart';
import '../../../../settings/presentation/pages/system_settings_page.dart';
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
/// Widget de navigation pour les différents types de dashboard
class DashboardNavigation extends StatefulWidget {
@@ -80,11 +86,11 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
Widget _buildBottomNavigationBar() {
return Container(
decoration: BoxDecoration(
color: DashboardTheme.white,
color: Theme.of(context).cardColor,
boxShadow: [
BoxShadow(
color: DashboardTheme.grey900.withOpacity(0.1),
blurRadius: 8,
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
@@ -92,10 +98,10 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
child: BottomAppBar(
shape: const CircularNotchedRectangle(),
notchMargin: 8,
color: DashboardTheme.white,
color: Theme.of(context).cardColor,
elevation: 0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: DashboardTheme.spacing8),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _tabs.asMap().entries.map((entry) {
@@ -121,23 +127,24 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
onTap: () => setState(() => _currentIndex = index),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: DashboardTheme.spacing12,
horizontal: DashboardTheme.spacing16,
vertical: 8,
horizontal: 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isActive ? tab.activeIcon : tab.icon,
color: isActive ? DashboardTheme.royalBlue : DashboardTheme.grey400,
size: 24,
color: isActive ? AppColors.primaryGreen : AppColors.textSecondaryLight,
size: 20,
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 4),
Text(
tab.title,
style: DashboardTheme.bodySmall.copyWith(
color: isActive ? DashboardTheme.royalBlue : DashboardTheme.grey400,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
style: AppTypography.badgeText.copyWith(
color: isActive ? AppColors.primaryGreen : AppColors.textSecondaryLight,
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
fontSize: 9,
),
),
],
@@ -147,21 +154,14 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
}
Widget _buildFloatingActionButton() {
return Container(
decoration: BoxDecoration(
gradient: DashboardTheme.primaryGradient,
borderRadius: BorderRadius.circular(28),
boxShadow: DashboardTheme.elevatedShadow,
),
child: FloatingActionButton(
onPressed: _showQuickActions,
backgroundColor: Colors.transparent,
elevation: 0,
child: const Icon(
Icons.add,
color: DashboardTheme.white,
size: 28,
),
return FloatingActionButton(
onPressed: _showQuickActions,
backgroundColor: AppColors.primaryGreen,
elevation: 4,
child: const Icon(
Icons.add_outlined,
color: Colors.white,
size: 28,
),
);
}
@@ -169,9 +169,9 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
Widget _buildReportsPage() {
return Scaffold(
appBar: AppBar(
title: const Text('Rapports'),
backgroundColor: DashboardTheme.royalBlue,
foregroundColor: DashboardTheme.white,
title: Text('Rapports'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 1.1)),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
automaticallyImplyLeading: false,
),
body: Center(
@@ -179,20 +179,20 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.assessment,
size: 64,
color: DashboardTheme.grey400,
Icons.assessment_outlined,
size: 48,
color: AppColors.textSecondaryLight,
),
const SizedBox(height: DashboardTheme.spacing16),
const Text(
'Page Rapports',
style: DashboardTheme.titleMedium,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 16),
Text(
'Fonctionnalité en cours de développement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey500,
'Page Rapports'.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'En cours de développement',
style: AppTypography.bodyTextSmall.copyWith(
color: AppColors.textSecondaryLight,
),
),
],
@@ -204,64 +204,64 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
Widget _buildSettingsPage() {
return Scaffold(
appBar: AppBar(
title: const Text('Paramètres'),
backgroundColor: DashboardTheme.royalBlue,
foregroundColor: DashboardTheme.white,
title: Text('Paramètres'.toUpperCase(), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 1.1)),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
automaticallyImplyLeading: false,
),
body: ListView(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
children: [
_buildSettingsSection(
'Apparence',
[
_buildSettingsTile(
'Thème',
'Bleu Roi & Pétrole',
Icons.palette,
() {},
'Design System UnionFlow',
Icons.palette_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
_buildSettingsTile(
'Langue',
'Français',
Icons.language,
() {},
Icons.language_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const LanguageSettingsPage())),
),
],
),
const SizedBox(height: DashboardTheme.spacing24),
const SizedBox(height: 24),
_buildSettingsSection(
'Notifications',
[
_buildSettingsTile(
'Notifications push',
'Activées',
Icons.notifications,
() {},
Icons.notifications_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
_buildSettingsTile(
'Emails',
'Quotidien',
Icons.email,
() {},
Icons.email_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
],
),
const SizedBox(height: DashboardTheme.spacing24),
const SizedBox(height: 24),
_buildSettingsSection(
'Données',
[
_buildSettingsTile(
'Synchronisation',
'Automatique',
Icons.sync,
() {},
Icons.sync_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
_buildSettingsTile(
'Cache',
'Vider le cache',
Icons.storage,
() {},
Icons.storage_outlined,
() => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
],
),
@@ -275,12 +275,16 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: DashboardTheme.titleMedium,
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, color: AppColors.primaryGreen, fontSize: 10),
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: 12),
Container(
decoration: DashboardTheme.cardDecoration,
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.lightBorder),
),
child: Column(children: children),
),
],
@@ -294,12 +298,13 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
VoidCallback onTap,
) {
return ListTile(
leading: Icon(icon, color: DashboardTheme.royalBlue),
title: Text(title, style: DashboardTheme.bodyMedium),
subtitle: Text(subtitle, style: DashboardTheme.bodySmall),
leading: Icon(icon, color: AppColors.primaryGreen, size: 20),
title: Text(title, style: AppTypography.actionText.copyWith(fontSize: 13)),
subtitle: Text(subtitle, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
trailing: const Icon(
Icons.chevron_right,
color: DashboardTheme.grey400,
Icons.chevron_right_outlined,
color: AppColors.textSecondaryLight,
size: 16,
),
onTap: onTap,
);
@@ -310,14 +315,14 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
context: context,
backgroundColor: Colors.transparent,
builder: (context) => Container(
decoration: const BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(DashboardTheme.borderRadiusLarge),
topRight: Radius.circular(DashboardTheme.borderRadiusLarge),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
padding: const EdgeInsets.all(DashboardTheme.spacing20),
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -325,61 +330,60 @@ class _DashboardNavigationState extends State<DashboardNavigation> {
width: 40,
height: 4,
decoration: BoxDecoration(
color: DashboardTheme.grey300,
color: AppColors.lightBorder,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: DashboardTheme.spacing20),
const Text(
'Actions Rapides',
style: DashboardTheme.titleMedium,
const SizedBox(height: 20),
Text(
'ACTIONS RAPIDES',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
),
const SizedBox(height: DashboardTheme.spacing20),
const SizedBox(height: 20),
GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: DashboardTheme.spacing16,
mainAxisSpacing: DashboardTheme.spacing16,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
children: [
_buildQuickActionItem('Nouveau\nMembre', Icons.person_add, DashboardTheme.success),
_buildQuickActionItem('Créer\nÉvénement', Icons.event_available, DashboardTheme.royalBlue),
_buildQuickActionItem('Ajouter\nContribution', Icons.payment, DashboardTheme.tealBlue),
_buildQuickActionItem('Envoyer\nMessage', Icons.message, DashboardTheme.warning),
_buildQuickActionItem('Générer\nRapport', Icons.assessment, DashboardTheme.info),
_buildQuickActionItem('Paramètres', Icons.settings, DashboardTheme.grey600),
_buildQuickActionItem(context, 'Nouveau\nMembre', Icons.person_add_outlined, AppColors.success, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper()))),
_buildQuickActionItem(context, 'Créer\nÉvénement', Icons.event_available_outlined, AppColors.primaryGreen, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper()))),
_buildQuickActionItem(context, 'Ajouter\nContribution', Icons.account_balance_wallet_outlined, AppColors.brandGreen, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()))),
_buildQuickActionItem(context, 'Générer\nRapport', Icons.assessment_outlined, AppColors.info, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper()))),
_buildQuickActionItem(context, 'Paramètres', Icons.settings_outlined, AppColors.textSecondaryLight, () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage()))),
],
),
const SizedBox(height: DashboardTheme.spacing20),
const SizedBox(height: 20),
],
),
),
);
}
Widget _buildQuickActionItem(String title, IconData icon, Color color) {
Widget _buildQuickActionItem(BuildContext context, String title, IconData icon, Color color, VoidCallback onNavigate) {
return GestureDetector(
onTap: () {
Navigator.pop(context);
// Action rapide non encore connectée
onNavigate();
},
child: Container(
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
border: Border.all(color: color.withOpacity(0.3)),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(DashboardTheme.spacing12),
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: DashboardTheme.spacing8),
Icon(icon, color: color, size: 20),
const SizedBox(height: 8),
Text(
title,
style: DashboardTheme.bodySmall.copyWith(
color: color,
fontWeight: FontWeight.w600,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textPrimaryLight,
fontSize: 9,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),

View File

@@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
import '../../../../adhesions/presentation/pages/adhesions_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../settings/presentation/pages/system_settings_page.dart';
/// Widget de notifications pour le dashboard
class DashboardNotificationsWidget extends StatelessWidget {
@@ -15,8 +19,8 @@ class DashboardNotificationsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: DashboardTheme.cardDecoration,
return CoreCard(
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -29,7 +33,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
final data = state is DashboardLoaded
? state.dashboardData
: (state as DashboardRefreshing).dashboardData;
return _buildNotifications(data);
return _buildNotifications(context, data);
} else if (state is DashboardError) {
return _buildErrorNotifications();
}
@@ -43,35 +47,36 @@ class DashboardNotificationsWidget extends StatelessWidget {
Widget _buildHeader(BuildContext context) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: DashboardTheme.royalBlue.withOpacity(0.1),
color: AppColors.primaryGreen.withOpacity(0.05),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(DashboardTheme.borderRadius),
topRight: Radius.circular(DashboardTheme.borderRadius),
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: DashboardTheme.royalBlue,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: AppColors.primaryGreen,
borderRadius: BorderRadius.circular(4),
),
child: const Icon(
Icons.notifications,
color: DashboardTheme.white,
size: 20,
Icons.notifications_outlined,
color: Colors.white,
size: 16,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 10),
Expanded(
child: Text(
'Notifications',
style: DashboardTheme.titleMedium.copyWith(
color: DashboardTheme.royalBlue,
'NOTIFICATIONS',
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.primaryGreen,
fontWeight: FontWeight.bold,
letterSpacing: 1.1,
),
),
),
@@ -81,23 +86,24 @@ class DashboardNotificationsWidget extends StatelessWidget {
final data = state is DashboardLoaded
? state.dashboardData
: (state as DashboardRefreshing).dashboardData;
final urgentCount = _getUrgentNotificationsCount(data);
final urgentCount = _getUrgentNotificationsCount(context, data);
if (urgentCount > 0) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing8,
vertical: DashboardTheme.spacing4,
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: DashboardTheme.error,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: AppColors.error,
borderRadius: BorderRadius.circular(4),
),
child: Text(
urgentCount.toString(),
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white,
style: AppTypography.badgeText.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 8,
),
),
);
@@ -111,8 +117,8 @@ class DashboardNotificationsWidget extends StatelessWidget {
);
}
Widget _buildNotifications(DashboardEntity data) {
final notifications = _generateNotifications(data);
Widget _buildNotifications(BuildContext context, DashboardEntity data) {
final notifications = _generateNotifications(context, data);
if (notifications.isEmpty) {
return _buildEmptyNotifications();
@@ -127,11 +133,11 @@ class DashboardNotificationsWidget extends StatelessWidget {
Widget _buildNotificationItem(DashboardNotification notification) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: const BoxDecoration(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: DashboardTheme.grey200,
color: AppColors.lightBorder,
width: 1,
),
),
@@ -140,18 +146,18 @@ class DashboardNotificationsWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: notification.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(4),
),
child: Icon(
notification.icon,
color: notification.color,
size: 20,
size: 16,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -161,7 +167,8 @@ class DashboardNotificationsWidget extends StatelessWidget {
Expanded(
child: Text(
notification.title,
style: DashboardTheme.bodyMedium.copyWith(
style: AppTypography.actionText.copyWith(
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
@@ -169,40 +176,41 @@ class DashboardNotificationsWidget extends StatelessWidget {
if (notification.isUrgent) ...[
Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing6,
vertical: DashboardTheme.spacing2,
horizontal: 4,
vertical: 1,
),
decoration: BoxDecoration(
color: DashboardTheme.error,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
color: AppColors.error,
borderRadius: BorderRadius.circular(2),
),
child: Text(
'URGENT',
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white,
style: AppTypography.badgeText.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 10,
fontSize: 7,
),
),
),
],
],
),
const SizedBox(height: DashboardTheme.spacing4),
const SizedBox(height: 2),
Text(
notification.message,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey600,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
fontSize: 10,
),
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Row(
children: [
Text(
notification.timeAgo,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey500,
fontSize: 11,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
fontSize: 9,
),
),
const Spacer(),
@@ -211,9 +219,10 @@ class DashboardNotificationsWidget extends StatelessWidget {
onTap: notification.onAction,
child: Text(
notification.actionLabel!,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.royalBlue,
fontWeight: FontWeight.w600,
style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen,
fontWeight: FontWeight.bold,
fontSize: 9,
),
),
),
@@ -229,76 +238,28 @@ class DashboardNotificationsWidget extends StatelessWidget {
}
Widget _buildLoadingNotifications() {
return Column(
children: List.generate(3, (index) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: DashboardTheme.grey200,
width: 1,
),
),
),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
),
const SizedBox(width: DashboardTheme.spacing12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 16,
width: double.infinity,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: DashboardTheme.spacing8),
Container(
height: 12,
width: 200,
decoration: BoxDecoration(
color: DashboardTheme.grey200,
borderRadius: BorderRadius.circular(4),
),
),
],
),
),
],
),
);
}),
return const Padding(
padding: EdgeInsets.all(20),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
}
Widget _buildErrorNotifications() {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing24),
padding: const EdgeInsets.all(24),
child: Center(
child: Column(
children: [
const Icon(
Icons.error_outline,
color: DashboardTheme.error,
size: 32,
color: AppColors.error,
size: 24,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Erreur de chargement',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.error,
'Erreur',
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.error,
),
),
],
@@ -309,28 +270,23 @@ class DashboardNotificationsWidget extends StatelessWidget {
Widget _buildEmptyNotifications() {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing24),
padding: const EdgeInsets.all(24),
child: Center(
child: Column(
children: [
const Icon(
Icons.notifications_none,
color: DashboardTheme.grey400,
size: 32,
Icons.notifications_none_outlined,
color: AppColors.textSecondaryLight,
size: 24,
),
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
Text(
'Aucune notification',
style: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey500,
),
'AUCUNE NOTIFICATION',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: DashboardTheme.spacing4),
Text(
'Vous êtes à jour !',
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey400,
),
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
),
],
),
@@ -338,20 +294,20 @@ class DashboardNotificationsWidget extends StatelessWidget {
);
}
List<DashboardNotification> _generateNotifications(DashboardEntity data) {
List<DashboardNotification> _generateNotifications(BuildContext context, DashboardEntity data) {
List<DashboardNotification> notifications = [];
// Notification pour les demandes en attente
if (data.stats.pendingRequests > 0) {
notifications.add(DashboardNotification(
title: 'Demandes en attente',
message: '${data.stats.pendingRequests} demandes nécessitent votre attention',
icon: Icons.pending_actions,
color: DashboardTheme.warning,
message: '${data.stats.pendingRequests} demandes à valider',
icon: Icons.pending_actions_outlined,
color: AppColors.warning,
timeAgo: '2h',
isUrgent: data.stats.pendingRequests > 20,
actionLabel: 'Voir',
onAction: () {},
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const AdhesionsPageWrapper())),
));
}
@@ -359,13 +315,13 @@ class DashboardNotificationsWidget extends StatelessWidget {
if (data.todayEventsCount > 0) {
notifications.add(DashboardNotification(
title: 'Événements aujourd\'hui',
message: '${data.todayEventsCount} événement(s) programmé(s) aujourd\'hui',
icon: Icons.event_available,
color: DashboardTheme.info,
message: '${data.todayEventsCount} événement(s) aujourd\'hui',
icon: Icons.event_available_outlined,
color: AppColors.info,
timeAgo: '30min',
isUrgent: false,
actionLabel: 'Voir',
onAction: () {},
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
));
}
@@ -373,9 +329,9 @@ class DashboardNotificationsWidget extends StatelessWidget {
if (data.stats.hasGrowth) {
notifications.add(DashboardNotification(
title: 'Croissance positive',
message: 'Croissance de ${data.stats.monthlyGrowth.toStringAsFixed(1)}% ce mois',
icon: Icons.trending_up,
color: DashboardTheme.success,
message: 'Progression de ${data.stats.monthlyGrowth.toStringAsFixed(1)}% ce mois',
icon: Icons.trending_up_outlined,
color: AppColors.success,
timeAgo: '1j',
isUrgent: false,
actionLabel: null,
@@ -386,14 +342,14 @@ class DashboardNotificationsWidget extends StatelessWidget {
// Notification pour l'engagement faible
if (!data.stats.isHighEngagement) {
notifications.add(DashboardNotification(
title: 'Engagement à améliorer',
message: 'Taux d\'engagement: ${(data.stats.engagementRate * 100).toStringAsFixed(0)}%',
icon: Icons.trending_down,
color: DashboardTheme.error,
title: 'Engagement à surveiller',
message: 'Taux: ${(data.stats.engagementRate * 100).toStringAsFixed(0)}%',
icon: Icons.trending_down_outlined,
color: AppColors.error,
timeAgo: '3h',
isUrgent: data.stats.engagementRate < 0.5,
actionLabel: 'Améliorer',
onAction: () {},
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
));
}
@@ -401,21 +357,21 @@ class DashboardNotificationsWidget extends StatelessWidget {
if (data.recentActivitiesCount > 0) {
notifications.add(DashboardNotification(
title: 'Nouvelles activités',
message: '${data.recentActivitiesCount} nouvelles activités aujourd\'hui',
icon: Icons.fiber_new,
color: DashboardTheme.tealBlue,
message: '${data.recentActivitiesCount} activités récentes',
icon: Icons.fiber_new_outlined,
color: AppColors.brandGreen,
timeAgo: '15min',
isUrgent: false,
actionLabel: 'Voir',
onAction: () {},
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
));
}
return notifications;
}
int _getUrgentNotificationsCount(DashboardEntity data) {
final notifications = _generateNotifications(data);
int _getUrgentNotificationsCount(BuildContext context, DashboardEntity data) {
final notifications = _generateNotifications(context, data);
return notifications.where((n) => n.isUrgent).length;
}
}

View File

@@ -1,5 +1,10 @@
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
import '../../../../settings/presentation/pages/system_settings_page.dart';
/// Widget de recherche rapide pour le dashboard
class DashboardSearchWidget extends StatefulWidget {
@@ -26,13 +31,14 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
late Animation<double> _scaleAnimation;
bool _isExpanded = false;
List<SearchSuggestion> _filteredSuggestions = [];
List<SearchSuggestion>? _defaultSuggestions;
@override
void initState() {
super.initState();
_setupAnimations();
_setupListeners();
_filteredSuggestions = widget.suggestions ?? _getDefaultSuggestions();
_filteredSuggestions = widget.suggestions ?? [];
}
void _setupAnimations() {
@@ -71,12 +77,13 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
void _filterSuggestions(String query) {
if (query.isEmpty) {
setState(() {
_filteredSuggestions = widget.suggestions ?? _getDefaultSuggestions();
_filteredSuggestions = widget.suggestions ?? _defaultSuggestions ?? [];
});
return;
}
final filtered = (widget.suggestions ?? _getDefaultSuggestions())
final defaultList = widget.suggestions ?? _defaultSuggestions ?? [];
final filtered = defaultList
.where((suggestion) =>
suggestion.title.toLowerCase().contains(query.toLowerCase()) ||
suggestion.subtitle.toLowerCase().contains(query.toLowerCase()))
@@ -89,11 +96,19 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
@override
Widget build(BuildContext context) {
if (_defaultSuggestions == null) {
_defaultSuggestions = _getDefaultSuggestions(context);
if (_filteredSuggestions.isEmpty && widget.suggestions == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) setState(() => _filteredSuggestions = _defaultSuggestions!);
});
}
}
return Column(
children: [
_buildSearchBar(),
if (_isExpanded && _filteredSuggestions.isNotEmpty) ...[
const SizedBox(height: DashboardTheme.spacing8),
const SizedBox(height: 8),
_buildSuggestions(),
],
],
@@ -108,9 +123,11 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
scale: _scaleAnimation.value,
child: Container(
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusLarge),
boxShadow: _isExpanded ? DashboardTheme.elevatedShadow : DashboardTheme.subtleShadow,
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
boxShadow: _isExpanded
? [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4))]
: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2))],
),
child: TextField(
controller: _searchController,
@@ -123,12 +140,13 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
},
decoration: InputDecoration(
hintText: widget.hintText ?? 'Rechercher...',
hintStyle: DashboardTheme.bodyMedium.copyWith(
color: DashboardTheme.grey400,
hintStyle: AppTypography.bodyTextSmall.copyWith(
color: AppColors.textSecondaryLight,
),
prefixIcon: Icon(
Icons.search,
color: _isExpanded ? DashboardTheme.royalBlue : DashboardTheme.grey400,
Icons.search_outlined,
color: _isExpanded ? AppColors.primaryGreen : AppColors.textSecondaryLight,
size: 20,
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
@@ -137,30 +155,31 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
_focusNode.unfocus();
},
icon: const Icon(
Icons.clear,
color: DashboardTheme.grey400,
Icons.close_outlined,
color: AppColors.textSecondaryLight,
size: 18,
),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusLarge),
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusLarge),
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: DashboardTheme.royalBlue,
width: 2,
color: AppColors.primaryGreen,
width: 1.5,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing16,
vertical: DashboardTheme.spacing12,
horizontal: 16,
vertical: 12,
),
filled: true,
fillColor: DashboardTheme.white,
fillColor: Theme.of(context).cardColor,
),
style: DashboardTheme.bodyMedium,
style: AppTypography.bodyTextSmall,
),
),
);
@@ -172,9 +191,15 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
return Container(
constraints: const BoxConstraints(maxHeight: 300),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.elevatedShadow,
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListView.builder(
shrinkWrap: true,
@@ -196,13 +221,13 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
suggestion.onTap?.call();
},
child: Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: isLast
? null
: const Border(
bottom: BorderSide(
color: DashboardTheme.grey200,
color: AppColors.lightBorder,
width: 1,
),
),
@@ -210,34 +235,36 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
child: Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: suggestion.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(6),
),
child: Icon(
suggestion.icon,
color: suggestion.color,
size: 20,
size: 18,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
suggestion.title,
style: DashboardTheme.bodyMedium.copyWith(
style: AppTypography.actionText.copyWith(
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
if (suggestion.subtitle.isNotEmpty) ...[
const SizedBox(height: DashboardTheme.spacing2),
const SizedBox(height: 2),
Text(
suggestion.subtitle,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.grey600,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
fontSize: 10,
),
),
],
@@ -245,8 +272,8 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
),
),
const Icon(
Icons.arrow_forward_ios,
color: DashboardTheme.grey400,
Icons.chevron_right_outlined,
color: AppColors.textSecondaryLight,
size: 16,
),
],
@@ -255,42 +282,42 @@ class _DashboardSearchWidgetState extends State<DashboardSearchWidget>
);
}
List<SearchSuggestion> _getDefaultSuggestions() {
List<SearchSuggestion> _getDefaultSuggestions(BuildContext context) {
return [
SearchSuggestion(
title: 'Membres',
subtitle: 'Rechercher des membres',
icon: Icons.people,
color: DashboardTheme.royalBlue,
onTap: () {},
icon: Icons.people_outline,
color: AppColors.primaryGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper())),
),
SearchSuggestion(
title: 'Événements',
subtitle: 'Trouver des événements',
icon: Icons.event,
color: DashboardTheme.tealBlue,
onTap: () {},
icon: Icons.event_outlined,
color: AppColors.brandGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
),
SearchSuggestion(
title: 'Contributions',
subtitle: 'Historique des paiements',
icon: Icons.payment,
color: DashboardTheme.success,
onTap: () {},
icon: Icons.account_balance_wallet_outlined,
color: AppColors.success,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper())),
),
SearchSuggestion(
title: 'Rapports',
subtitle: 'Consulter les rapports',
icon: Icons.assessment,
color: DashboardTheme.warning,
onTap: () {},
icon: Icons.assessment_outlined,
color: AppColors.warning,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper())),
),
SearchSuggestion(
title: 'Paramètres',
subtitle: 'Configuration système',
icon: Icons.settings,
color: DashboardTheme.grey600,
onTap: () {},
icon: Icons.settings_outlined,
color: AppColors.textSecondaryLight,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
),
];
}

View File

@@ -1,6 +1,10 @@
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/dashboard_theme_manager.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../../shared/design_system/tokens/app_typography.dart';
import '../../../../../shared/design_system/tokens/spacing_tokens.dart';
import '../../../../../shared/design_system/tokens/radius_tokens.dart';
import '../../../../../shared/widgets/core_card.dart';
/// Widget de sélection de thème pour le Dashboard
class ThemeSelectorWidget extends StatefulWidget {
@@ -27,13 +31,8 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
decoration: BoxDecoration(
color: DashboardTheme.white,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
boxShadow: DashboardTheme.subtleShadow,
),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -41,17 +40,17 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
children: [
Icon(
Icons.palette,
color: DashboardTheme.royalBlue,
color: AppColors.primaryGreen,
size: 24,
),
SizedBox(width: DashboardTheme.spacing8),
SizedBox(width: 8),
Text(
'Thème de l\'interface',
style: DashboardTheme.titleMedium,
style: AppTypography.headerSmall,
),
],
),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: SpacingTokens.xl),
// Grille des thèmes
GridView.builder(
@@ -59,8 +58,8 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: DashboardTheme.spacing12,
mainAxisSpacing: DashboardTheme.spacing12,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.5,
),
itemCount: DashboardThemeManager.availableThemes.length,
@@ -72,7 +71,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
},
),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: SpacingTokens.xl),
// Aperçu du thème sélectionné
_buildThemePreview(),
@@ -87,11 +86,11 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected
? themeOption.theme.primaryColor
: DashboardTheme.grey300,
: const Color(0xFFD1D5DB),
width: isSelected ? 2 : 1,
),
boxShadow: isSelected
@@ -102,7 +101,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
offset: const Offset(0, 2),
),
]
: DashboardTheme.subtleShadow,
: null,
),
child: Column(
children: [
@@ -121,8 +120,8 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
end: Alignment.bottomRight,
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(DashboardTheme.borderRadius - 1),
topRight: Radius.circular(DashboardTheme.borderRadius - 1),
topLeft: Radius.circular(RadiusTokens.lg - 1),
topRight: Radius.circular(RadiusTokens.lg - 1),
),
),
child: isSelected
@@ -140,12 +139,12 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
flex: 1,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(DashboardTheme.spacing8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: themeOption.theme.cardColor,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(DashboardTheme.borderRadius - 1),
bottomRight: Radius.circular(DashboardTheme.borderRadius - 1),
bottomLeft: Radius.circular(7),
bottomRight: Radius.circular(7),
),
),
child: Center(
@@ -172,11 +171,11 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
.firstWhere((theme) => theme.key == _selectedTheme);
return Container(
padding: const EdgeInsets.all(DashboardTheme.spacing16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: currentTheme.theme.backgroundColor,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
border: Border.all(color: DashboardTheme.grey300),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFD1D5DB)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -189,15 +188,15 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
color: currentTheme.theme.textPrimary,
),
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: SpacingTokens.lg),
// Exemple de carte avec le thème
// Aperçu de carte avec le thème
Container(
width: double.infinity,
padding: const EdgeInsets.all(DashboardTheme.spacing12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: currentTheme.theme.cardColor,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(4),
boxShadow: [
BoxShadow(
color: currentTheme.theme.primaryColor.withOpacity(0.1),
@@ -221,7 +220,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
size: 20,
),
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: SpacingTokens.lg),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -236,7 +235,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
),
const SizedBox(height: 2),
Text(
'Exemple avec ce thème',
'Aperçu',
style: TextStyle(
fontSize: 12,
color: currentTheme.theme.textSecondary,
@@ -247,12 +246,12 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing8,
vertical: DashboardTheme.spacing4,
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: currentTheme.theme.success.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Actif',
@@ -267,17 +266,17 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
),
),
const SizedBox(height: DashboardTheme.spacing12),
const SizedBox(height: SpacingTokens.lg),
// Palette de couleurs
Row(
children: [
_buildColorSwatch('Primaire', currentTheme.theme.primaryColor),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
_buildColorSwatch('Secondaire', currentTheme.theme.secondaryColor),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
_buildColorSwatch('Succès', currentTheme.theme.success),
const SizedBox(width: DashboardTheme.spacing8),
const SizedBox(width: 8),
_buildColorSwatch('Attention', currentTheme.theme.warning),
],
),
@@ -295,7 +294,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
height: 30,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 4),
@@ -303,7 +302,7 @@ class _ThemeSelectorWidgetState extends State<ThemeSelectorWidget> {
label,
style: const TextStyle(
fontSize: 10,
color: DashboardTheme.grey600,
color: Color(0xFF4B5563),
),
textAlign: TextAlign.center,
),

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/dashboard_theme.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../../shared/widgets/core_card.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
@@ -22,14 +23,13 @@ class DashboardShortcutsWidget extends StatelessWidget {
final shortcuts = customShortcuts ?? _getDefaultShortcuts(context);
final displayShortcuts = shortcuts.take(maxShortcuts).toList();
return Container(
decoration: DashboardTheme.cardDecoration,
padding: const EdgeInsets.all(DashboardTheme.spacing20),
return CoreCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: DashboardTheme.spacing16),
const SizedBox(height: 16),
_buildShortcutsGrid(displayShortcuts),
],
),
@@ -39,36 +39,18 @@ class DashboardShortcutsWidget extends StatelessWidget {
Widget _buildHeader() {
return Row(
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing8),
decoration: BoxDecoration(
color: DashboardTheme.tealBlue.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: const Icon(
Icons.flash_on,
color: DashboardTheme.tealBlue,
size: 20,
),
const Icon(
Icons.flash_on_outlined,
color: AppColors.primaryGreen,
size: 18,
),
const SizedBox(width: DashboardTheme.spacing12),
const SizedBox(width: 8),
Expanded(
child: Text(
'Actions Rapides',
style: DashboardTheme.titleMedium.copyWith(
'ACTIONS RAPIDES',
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
),
),
),
TextButton(
onPressed: () {
// Personnalisation des raccourcis non encore implémentée
},
child: Text(
'Personnaliser',
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.tealBlue,
fontWeight: FontWeight.w600,
letterSpacing: 1.1,
),
),
),
@@ -82,9 +64,9 @@ class DashboardShortcutsWidget extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: DashboardTheme.spacing12,
mainAxisSpacing: DashboardTheme.spacing12,
childAspectRatio: 1.0,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.9,
),
itemCount: shortcuts.length,
itemBuilder: (context, index) {
@@ -96,64 +78,34 @@ class DashboardShortcutsWidget extends StatelessWidget {
Widget _buildShortcutItem(DashboardShortcut shortcut) {
return GestureDetector(
onTap: shortcut.onTap,
child: Container(
decoration: BoxDecoration(
color: shortcut.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadius),
border: Border.all(
color: shortcut.color.withOpacity(0.3),
width: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: shortcut.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
shortcut.icon,
color: shortcut.color,
size: 20,
),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(DashboardTheme.spacing12),
decoration: BoxDecoration(
color: shortcut.color.withOpacity(0.2),
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusLarge),
),
child: Icon(
shortcut.icon,
color: shortcut.color,
size: 24,
),
const SizedBox(height: 8),
Text(
shortcut.title,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textPrimaryLight,
fontSize: 9,
fontWeight: FontWeight.w500,
),
const SizedBox(height: DashboardTheme.spacing8),
Text(
shortcut.title,
style: DashboardTheme.bodySmall.copyWith(
color: shortcut.color,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (shortcut.badge != null) ...[
const SizedBox(height: DashboardTheme.spacing4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: DashboardTheme.spacing6,
vertical: DashboardTheme.spacing2,
),
decoration: BoxDecoration(
color: shortcut.badgeColor ?? DashboardTheme.error,
borderRadius: BorderRadius.circular(DashboardTheme.borderRadiusSmall),
),
child: Text(
shortcut.badge!,
style: DashboardTheme.bodySmall.copyWith(
color: DashboardTheme.white,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
),
],
],
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
@@ -162,8 +114,8 @@ class DashboardShortcutsWidget extends StatelessWidget {
return [
DashboardShortcut(
title: 'Nouveau\nMembre',
icon: Icons.person_add,
color: DashboardTheme.success,
icon: Icons.person_add_outlined,
color: AppColors.success,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
@@ -174,8 +126,8 @@ class DashboardShortcutsWidget extends StatelessWidget {
),
DashboardShortcut(
title: 'Créer\nÉvénement',
icon: Icons.event_available,
color: DashboardTheme.royalBlue,
icon: Icons.event_available_outlined,
color: AppColors.primaryGreen,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
@@ -186,32 +138,20 @@ class DashboardShortcutsWidget extends StatelessWidget {
),
DashboardShortcut(
title: 'Ajouter\nContribution',
icon: Icons.payment,
color: DashboardTheme.tealBlue,
icon: Icons.account_balance_wallet_outlined,
color: AppColors.brandGreen,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ContributionsPageWrapper(),
builder: (context) => const CotisationsPageWrapper(),
),
);
},
),
DashboardShortcut(
title: 'Envoyer\nMessage',
icon: Icons.message,
color: DashboardTheme.warning,
badge: '3',
badgeColor: DashboardTheme.error,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Messagerie à venir')),
);
},
),
DashboardShortcut(
title: 'Générer\nRapport',
icon: Icons.assessment,
color: DashboardTheme.info,
icon: Icons.assessment_outlined,
color: AppColors.info,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
@@ -222,8 +162,8 @@ class DashboardShortcutsWidget extends StatelessWidget {
),
DashboardShortcut(
title: 'Paramètres',
icon: Icons.settings,
color: DashboardTheme.grey600,
icon: Icons.settings_outlined,
color: AppColors.textSecondaryLight,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(