400 lines
13 KiB
Dart
400 lines
13 KiB
Dart
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/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 {
|
|
final int maxNotifications;
|
|
|
|
const DashboardNotificationsWidget({
|
|
super.key,
|
|
this.maxNotifications = 5,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return CoreCard(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHeader(context),
|
|
BlocBuilder<DashboardBloc, DashboardState>(
|
|
builder: (context, state) {
|
|
if (state is DashboardLoading) {
|
|
return _buildLoadingNotifications();
|
|
} else if (state is DashboardLoaded || state is DashboardRefreshing) {
|
|
final data = state is DashboardLoaded
|
|
? state.dashboardData
|
|
: (state as DashboardRefreshing).dashboardData;
|
|
return _buildNotifications(context, data);
|
|
} else if (state is DashboardError) {
|
|
return _buildErrorNotifications();
|
|
}
|
|
return _buildEmptyNotifications();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primaryGreen.withOpacity(0.05),
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(12),
|
|
topRight: Radius.circular(12),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primaryGreen,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: const Icon(
|
|
Icons.notifications_outlined,
|
|
color: Colors.white,
|
|
size: 16,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
'NOTIFICATIONS',
|
|
style: AppTypography.subtitleSmall.copyWith(
|
|
color: AppColors.primaryGreen,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1.1,
|
|
),
|
|
),
|
|
),
|
|
BlocBuilder<DashboardBloc, DashboardState>(
|
|
builder: (context, state) {
|
|
if (state is DashboardLoaded || state is DashboardRefreshing) {
|
|
final data = state is DashboardLoaded
|
|
? state.dashboardData
|
|
: (state as DashboardRefreshing).dashboardData;
|
|
final urgentCount = _getUrgentNotificationsCount(context, data);
|
|
|
|
if (urgentCount > 0) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 6,
|
|
vertical: 2,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.error,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Text(
|
|
urgentCount.toString(),
|
|
style: AppTypography.badgeText.copyWith(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 8,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNotifications(BuildContext context, DashboardEntity data) {
|
|
final notifications = _generateNotifications(context, data);
|
|
|
|
if (notifications.isEmpty) {
|
|
return _buildEmptyNotifications();
|
|
}
|
|
|
|
return Column(
|
|
children: notifications.take(maxNotifications).map((notification) {
|
|
return _buildNotificationItem(notification);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
Widget _buildNotificationItem(DashboardNotification notification) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: AppColors.lightBorder,
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: notification.color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Icon(
|
|
notification.icon,
|
|
color: notification.color,
|
|
size: 16,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
notification.title,
|
|
style: AppTypography.actionText.copyWith(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
if (notification.isUrgent) ...[
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 4,
|
|
vertical: 1,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.error,
|
|
borderRadius: BorderRadius.circular(2),
|
|
),
|
|
child: Text(
|
|
'URGENT',
|
|
style: AppTypography.badgeText.copyWith(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 7,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
notification.message,
|
|
style: AppTypography.subtitleSmall.copyWith(
|
|
color: AppColors.textSecondaryLight,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
notification.timeAgo,
|
|
style: AppTypography.subtitleSmall.copyWith(
|
|
color: AppColors.textSecondaryLight,
|
|
fontSize: 9,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
if (notification.actionLabel != null) ...[
|
|
GestureDetector(
|
|
onTap: notification.onAction,
|
|
child: Text(
|
|
notification.actionLabel!,
|
|
style: AppTypography.badgeText.copyWith(
|
|
color: AppColors.primaryGreen,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 9,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingNotifications() {
|
|
return const Padding(
|
|
padding: EdgeInsets.all(20),
|
|
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorNotifications() {
|
|
return Container(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
const Icon(
|
|
Icons.error_outline,
|
|
color: AppColors.error,
|
|
size: 24,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Erreur',
|
|
style: AppTypography.subtitleSmall.copyWith(
|
|
color: AppColors.error,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyNotifications() {
|
|
return Container(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
const Icon(
|
|
Icons.notifications_none_outlined,
|
|
color: AppColors.textSecondaryLight,
|
|
size: 24,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'AUCUNE NOTIFICATION',
|
|
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
|
|
),
|
|
Text(
|
|
'Vous êtes à jour !',
|
|
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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 à valider',
|
|
icon: Icons.pending_actions_outlined,
|
|
color: AppColors.warning,
|
|
timeAgo: '2h',
|
|
isUrgent: data.stats.pendingRequests > 20,
|
|
actionLabel: 'Voir',
|
|
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const AdhesionsPageWrapper())),
|
|
));
|
|
}
|
|
|
|
// Notification pour les événements aujourd'hui
|
|
if (data.todayEventsCount > 0) {
|
|
notifications.add(DashboardNotification(
|
|
title: 'Événements aujourd\'hui',
|
|
message: '${data.todayEventsCount} événement(s) aujourd\'hui',
|
|
icon: Icons.event_available_outlined,
|
|
color: AppColors.info,
|
|
timeAgo: '30min',
|
|
isUrgent: false,
|
|
actionLabel: 'Voir',
|
|
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
|
|
));
|
|
}
|
|
|
|
// Notification pour la croissance
|
|
if (data.stats.hasGrowth) {
|
|
notifications.add(DashboardNotification(
|
|
title: 'Croissance positive',
|
|
message: 'Progression de ${data.stats.monthlyGrowth.toStringAsFixed(1)}% ce mois',
|
|
icon: Icons.trending_up_outlined,
|
|
color: AppColors.success,
|
|
timeAgo: '1j',
|
|
isUrgent: false,
|
|
actionLabel: null,
|
|
onAction: null,
|
|
));
|
|
}
|
|
|
|
// Notification pour l'engagement faible
|
|
if (!data.stats.isHighEngagement) {
|
|
notifications.add(DashboardNotification(
|
|
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: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const SystemSettingsPage())),
|
|
));
|
|
}
|
|
|
|
// Notification pour les nouveaux membres
|
|
if (data.recentActivitiesCount > 0) {
|
|
notifications.add(DashboardNotification(
|
|
title: 'Nouvelles activités',
|
|
message: '${data.recentActivitiesCount} activités récentes',
|
|
icon: Icons.fiber_new_outlined,
|
|
color: AppColors.brandGreen,
|
|
timeAgo: '15min',
|
|
isUrgent: false,
|
|
actionLabel: 'Voir',
|
|
onAction: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
|
|
));
|
|
}
|
|
|
|
return notifications;
|
|
}
|
|
|
|
int _getUrgentNotificationsCount(BuildContext context, DashboardEntity data) {
|
|
final notifications = _generateNotifications(context, data);
|
|
return notifications.where((n) => n.isUrgent).length;
|
|
}
|
|
}
|
|
|
|
class DashboardNotification {
|
|
final String title;
|
|
final String message;
|
|
final IconData icon;
|
|
final Color color;
|
|
final String timeAgo;
|
|
final bool isUrgent;
|
|
final String? actionLabel;
|
|
final VoidCallback? onAction;
|
|
|
|
const DashboardNotification({
|
|
required this.title,
|
|
required this.message,
|
|
required this.icon,
|
|
required this.color,
|
|
required this.timeAgo,
|
|
required this.isUrgent,
|
|
this.actionLabel,
|
|
this.onAction,
|
|
});
|
|
}
|