Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,262 @@
import 'package:flutter/material.dart';
import '../../theme/app_theme.dart';
import '../cards/unified_card_widget.dart';
/// Section KPI unifiée pour afficher des indicateurs clés
///
/// Fournit :
/// - Cartes KPI avec animations
/// - Layouts adaptatifs (grille ou liste)
/// - Indicateurs de tendance
/// - Couleurs thématiques
class UnifiedKPISection extends StatelessWidget {
/// Liste des KPI à afficher
final List<UnifiedKPIData> kpis;
/// Titre de la section
final String? title;
/// Nombre de colonnes dans la grille (par défaut : 2)
final int crossAxisCount;
/// Espacement entre les cartes
final double spacing;
/// Callback lors du tap sur un KPI
final void Function(UnifiedKPIData kpi)? onKPITap;
const UnifiedKPISection({
super.key,
required this.kpis,
this.title,
this.crossAxisCount = 2,
this.spacing = 16.0,
this.onKPITap,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null) ...[
Text(
title!,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
],
_buildKPIGrid(),
],
);
}
Widget _buildKPIGrid() {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
childAspectRatio: 1.4,
),
itemCount: kpis.length,
itemBuilder: (context, index) {
final kpi = kpis[index];
return UnifiedCard.kpi(
onTap: onKPITap != null ? () => onKPITap!(kpi) : null,
child: _buildKPIContent(kpi),
);
},
);
}
Widget _buildKPIContent(UnifiedKPIData kpi) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// En-tête avec icône et titre
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: kpi.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
kpi.icon,
color: kpi.color,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
kpi.title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppTheme.textSecondary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 12),
// Valeur principale
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Text(
kpi.value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: AppTheme.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (kpi.trend != null) ...[
const SizedBox(width: 8),
_buildTrendIndicator(kpi.trend!),
],
],
),
// Sous-titre ou description
if (kpi.subtitle != null) ...[
const SizedBox(height: 4),
Text(
kpi.subtitle!,
style: const TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
],
);
}
Widget _buildTrendIndicator(UnifiedKPITrend trend) {
IconData icon;
Color color;
switch (trend.direction) {
case UnifiedKPITrendDirection.up:
icon = Icons.trending_up;
color = AppTheme.successColor;
break;
case UnifiedKPITrendDirection.down:
icon = Icons.trending_down;
color = AppTheme.errorColor;
break;
case UnifiedKPITrendDirection.stable:
icon = Icons.trending_flat;
color = AppTheme.textSecondary;
break;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 12,
color: color,
),
const SizedBox(width: 2),
Text(
trend.value,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: color,
),
),
],
),
);
}
}
/// Données pour un KPI unifié
class UnifiedKPIData {
/// Titre du KPI
final String title;
/// Valeur principale à afficher
final String value;
/// Sous-titre ou description optionnelle
final String? subtitle;
/// Icône représentative
final IconData icon;
/// Couleur thématique
final Color color;
/// Indicateur de tendance optionnel
final UnifiedKPITrend? trend;
/// Données supplémentaires pour les callbacks
final Map<String, dynamic>? metadata;
const UnifiedKPIData({
required this.title,
required this.value,
required this.icon,
required this.color,
this.subtitle,
this.trend,
this.metadata,
});
}
/// Indicateur de tendance pour les KPI
class UnifiedKPITrend {
/// Direction de la tendance
final UnifiedKPITrendDirection direction;
/// Valeur de la tendance (ex: "+12%", "-5", "stable")
final String value;
/// Label descriptif de la tendance (ex: "ce mois", "vs mois dernier")
final String? label;
const UnifiedKPITrend({
required this.direction,
required this.value,
this.label,
});
}
/// Direction de tendance disponibles
enum UnifiedKPITrendDirection {
up,
down,
stable,
}

View File

@@ -0,0 +1,262 @@
import 'package:flutter/material.dart';
import '../../theme/app_theme.dart';
import '../cards/unified_card_widget.dart';
/// Section d'actions rapides unifiée
///
/// Fournit :
/// - Grille d'actions avec icônes
/// - Animations au tap
/// - Layouts adaptatifs
/// - Badges de notification
class UnifiedQuickActionsSection extends StatelessWidget {
/// Liste des actions rapides
final List<UnifiedQuickAction> actions;
/// Titre de la section
final String? title;
/// Nombre de colonnes dans la grille (par défaut : 3)
final int crossAxisCount;
/// Espacement entre les actions
final double spacing;
/// Callback lors du tap sur une action
final void Function(UnifiedQuickAction action)? onActionTap;
const UnifiedQuickActionsSection({
super.key,
required this.actions,
this.title,
this.crossAxisCount = 3,
this.spacing = 12.0,
this.onActionTap,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null) ...[
Text(
title!,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 16),
],
_buildActionsGrid(),
],
);
}
Widget _buildActionsGrid() {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
childAspectRatio: 1.0,
),
itemCount: actions.length,
itemBuilder: (context, index) {
final action = actions[index];
return _buildActionCard(action);
},
);
}
Widget _buildActionCard(UnifiedQuickAction action) {
return UnifiedCard(
onTap: action.enabled && onActionTap != null
? () => onActionTap!(action)
: null,
variant: UnifiedCardVariant.outlined,
padding: const EdgeInsets.all(12),
child: Stack(
children: [
// Contenu principal
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icône avec conteneur coloré
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: action.enabled
? action.color.withOpacity(0.1)
: AppTheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
action.icon,
color: action.enabled
? action.color
: AppTheme.textSecondary.withOpacity(0.5),
size: 24,
),
),
const SizedBox(height: 8),
// Titre de l'action
Text(
action.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: action.enabled
? AppTheme.textPrimary
: AppTheme.textSecondary.withOpacity(0.5),
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
// Badge de notification
if (action.badgeCount != null && action.badgeCount! > 0)
Positioned(
top: 0,
right: 0,
child: _buildBadge(action.badgeCount!),
),
// Indicateur "nouveau"
if (action.isNew)
Positioned(
top: 4,
right: 4,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: AppTheme.accentColor,
shape: BoxShape.circle,
),
),
),
],
),
);
}
Widget _buildBadge(int count) {
final displayCount = count > 99 ? '99+' : count.toString();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppTheme.errorColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white, width: 2),
),
constraints: const BoxConstraints(
minWidth: 20,
minHeight: 20,
),
child: Text(
displayCount,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
);
}
}
/// Données pour une action rapide unifiée
class UnifiedQuickAction {
/// Identifiant unique de l'action
final String id;
/// Titre de l'action
final String title;
/// Icône représentative
final IconData icon;
/// Couleur thématique
final Color color;
/// Indique si l'action est activée
final bool enabled;
/// Nombre de notifications/badges (optionnel)
final int? badgeCount;
/// Indique si l'action est nouvelle
final bool isNew;
/// Données supplémentaires pour les callbacks
final Map<String, dynamic>? metadata;
const UnifiedQuickAction({
required this.id,
required this.title,
required this.icon,
required this.color,
this.enabled = true,
this.badgeCount,
this.isNew = false,
this.metadata,
});
}
/// Actions rapides prédéfinies communes
class CommonQuickActions {
static const UnifiedQuickAction addMember = UnifiedQuickAction(
id: 'add_member',
title: 'Ajouter\nMembre',
icon: Icons.person_add,
color: AppTheme.primaryColor,
);
static const UnifiedQuickAction addEvent = UnifiedQuickAction(
id: 'add_event',
title: 'Nouvel\nÉvénement',
icon: Icons.event_available,
color: AppTheme.accentColor,
);
static const UnifiedQuickAction collectPayment = UnifiedQuickAction(
id: 'collect_payment',
title: 'Collecter\nCotisation',
icon: Icons.payment,
color: AppTheme.successColor,
);
static const UnifiedQuickAction sendMessage = UnifiedQuickAction(
id: 'send_message',
title: 'Envoyer\nMessage',
icon: Icons.message,
color: AppTheme.infoColor,
);
static const UnifiedQuickAction generateReport = UnifiedQuickAction(
id: 'generate_report',
title: 'Générer\nRapport',
icon: Icons.assessment,
color: AppTheme.warningColor,
);
static const UnifiedQuickAction manageSettings = UnifiedQuickAction(
id: 'manage_settings',
title: 'Paramètres',
icon: Icons.settings,
color: AppTheme.textSecondary,
);
}