refactoring

This commit is contained in:
dahoud
2026-03-31 09:14:47 +00:00
parent 9bfffeeebe
commit 5383df6dcb
200 changed files with 11192 additions and 7063 deletions

View File

@@ -1,6 +1,6 @@
/// UnionFlow Primary Button - Bouton principal
///
/// Bouton primaire avec la couleur Bleu Roi (#4169E1)
///
/// Bouton primaire Vert Forêt (#2E7D32)
/// Utilisé pour les actions principales (connexion, enregistrer, valider, etc.)
library uf_primary_button;
@@ -59,22 +59,22 @@ class UFPrimaryButton extends StatelessWidget {
Widget build(BuildContext context) {
return SizedBox(
width: isFullWidth ? double.infinity : null,
height: height ?? SpacingTokens.buttonHeightLarge,
height: height ?? SpacingTokens.buttonHeightMedium,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? AppColors.primaryGreen,
foregroundColor: textColor ?? Colors.white,
backgroundColor: backgroundColor ?? AppColors.primaryGreen,
foregroundColor: textColor ?? Colors.white,
disabledBackgroundColor: (backgroundColor ?? AppColors.primaryGreen).withOpacity(0.5),
disabledForegroundColor: (textColor ?? Colors.white).withOpacity(0.7),
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
child: isLoading

View File

@@ -1,6 +1,6 @@
/// UnionFlow Secondary Button - Bouton secondaire
///
/// Bouton secondaire avec la couleur Indigo (#6366F1)
///
/// Bouton secondaire Vert Menthe (#4CAF50)
/// Utilisé pour les actions secondaires (annuler, retour, etc.)
library uf_secondary_button;
@@ -30,22 +30,22 @@ class UFSecondaryButton extends StatelessWidget {
Widget build(BuildContext context) {
return SizedBox(
width: isFullWidth ? double.infinity : null,
height: height ?? SpacingTokens.buttonHeightLarge,
height: height ?? SpacingTokens.buttonHeightMedium,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.brandGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.brandGreen,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.brandGreen.withOpacity(0.5),
disabledForegroundColor: Colors.white.withOpacity(0.7),
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
child: isLoading

View File

@@ -83,9 +83,9 @@ class UFCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.cardPadding);
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.lg);
final effectiveMargin = margin ?? EdgeInsets.zero;
final effectiveBorderRadius = borderRadius ?? SpacingTokens.radiusLg;
final effectiveBorderRadius = borderRadius ?? SpacingTokens.radiusMd;
Widget content = Container(
padding: effectivePadding,
@@ -114,15 +114,6 @@ class UFCard extends StatelessWidget {
return BoxDecoration(
color: color ?? AppColors.lightSurface,
borderRadius: BorderRadius.circular(radius),
boxShadow: elevation != null
? [
BoxShadow(
color: AppColors.darkBorder.withOpacity(0.1),
blurRadius: elevation!,
offset: const Offset(0, 2),
),
]
: ShadowTokens.sm,
);
case UFCardStyle.outlined:

View File

@@ -49,15 +49,15 @@ class UFInfoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.xl);
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.lg);
return Container(
padding: effectivePadding,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
boxShadow: ShadowTokens.sm,
color: isDark ? AppColors.darkSurface : Colors.white,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -65,20 +65,20 @@ class UFInfoCard extends StatelessWidget {
// Header avec titre et trailing
Row(
children: [
Icon(icon, color: effectiveIconColor, size: 20),
Icon(icon, color: effectiveIconColor, size: 16),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
),
),
),
if (trailing != null) trailing!,
],
),
const SizedBox(height: SpacingTokens.xl),
const SizedBox(height: SpacingTokens.lg),
// Contenu
child,
],

View File

@@ -45,7 +45,7 @@ class UFMetricCard extends StatelessWidget {
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Column(
children: [

View File

@@ -55,21 +55,22 @@ class UFStatCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
final effectiveIconBgColor = iconBackgroundColor ??
final effectiveIconBgColor = iconBackgroundColor ??
effectiveIconColor.withOpacity(0.1);
return Card(
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.cardPadding),
padding: const EdgeInsets.all(SpacingTokens.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -79,55 +80,55 @@ class UFStatCard extends StatelessWidget {
children: [
// Icône avec background coloré
Container(
padding: const EdgeInsets.all(SpacingTokens.md),
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: effectiveIconBgColor,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
),
child: Icon(
icon,
color: effectiveIconColor,
size: 24,
size: 18,
),
),
const Spacer(),
// Flèche si cliquable
if (onTap != null)
const Icon(
Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.textSecondaryLight,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),
],
),
const SizedBox(height: SpacingTokens.lg),
const SizedBox(height: SpacingTokens.md),
// Titre
Text(
title,
style: AppTypography.badgeText.copyWith(
color: AppColors.textSecondaryLight,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),
),
const SizedBox(height: SpacingTokens.sm),
// Valeur
Text(
value,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
),
),
// Sous-titre optionnel
if (subtitle != null) ...[
const SizedBox(height: SpacingTokens.sm),
Text(
subtitle!,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),
),
],

View File

@@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Ligne d'activité récente — style fintech compact identique au super_admin
/// Icône dans carré arrondi 28×28 + titre + description + timestamp
class DashboardActivityRow extends StatelessWidget {
final String title;
final String description;
final String timeAgo;
final IconData icon;
final Color color;
const DashboardActivityRow({
super.key,
required this.title,
required this.description,
required this.timeAgo,
required this.icon,
required this.color,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: UnionFlowColors.border),
),
child: Row(
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: color.withOpacity(0.12),
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, size: 14, color: color),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
description,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 8),
Text(
timeAgo,
style: const TextStyle(fontSize: 10, color: UnionFlowColors.textTertiary),
),
],
),
);
}
/// Icône selon le type d'activité
static IconData iconFor(String type) {
switch (type) {
case 'member':
return Icons.person_add_rounded;
case 'event':
return Icons.event_rounded;
case 'contribution':
return Icons.payments_rounded;
default:
return Icons.circle_notifications_rounded;
}
}
/// Couleur selon le type d'activité
static Color colorFor(String type) {
switch (type) {
case 'member':
return UnionFlowColors.unionGreen;
case 'event':
return UnionFlowColors.info;
case 'contribution':
return UnionFlowColors.gold;
default:
return UnionFlowColors.textSecondary;
}
}
}

View File

@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Ligne d'événement à venir — style fintech avec bordure gauche verte
/// Titre + date + countdown optionnel + participants optionnel
class DashboardEventRow extends StatelessWidget {
final String title;
final String date;
final String? daysUntil;
final String? participants;
const DashboardEventRow({
super.key,
required this.title,
required this.date,
this.daysUntil,
this.participants,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(8),
border: const Border(
left: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
date,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
if (daysUntil != null || participants != null)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (daysUntil != null)
Text(
daysUntil!,
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: UnionFlowColors.unionGreen,
),
),
if (participants != null) ...[
const SizedBox(height: 2),
Text(
participants!,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textTertiary,
),
),
],
],
),
],
),
);
}
}

View File

@@ -48,7 +48,9 @@ class UFDropdownTile<T> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final effectiveBgColor = backgroundColor ?? AppColors.lightSurface;
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveBgColor = backgroundColor ??
(isDark ? AppColors.darkSurface : AppColors.lightSurface);
final effectiveItemBuilder = itemBuilder ?? (item) => item.toString();
return Container(
@@ -65,16 +67,16 @@ class UFDropdownTile<T> extends StatelessWidget {
title,
style: AppTypography.bodyTextSmall.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
color: isDark ? AppColors.darkBackground : Colors.white,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(color: AppColors.lightBorder),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(

View File

@@ -44,7 +44,9 @@ class UFSwitchTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final effectiveBgColor = backgroundColor ?? AppColors.lightSurface;
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveBgColor = backgroundColor ??
(isDark ? AppColors.darkSurface : AppColors.lightSurface);
return Container(
margin: const EdgeInsets.only(bottom: SpacingTokens.lg),
@@ -63,13 +65,13 @@ class UFSwitchTile extends StatelessWidget {
title,
style: AppTypography.bodyTextSmall.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
),
),
Text(
subtitle,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),
),
],

View File

@@ -65,7 +65,7 @@ class UFContainer extends StatelessWidget {
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusLg;
}) : borderRadius = SpacingTokens.radiusMd;
/// Container très arrondi
const UFContainer.extraRounded({
@@ -81,7 +81,7 @@ class UFContainer extends StatelessWidget {
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusXl;
}) : borderRadius = SpacingTokens.radiusLg;
/// Container avec ombre
UFContainer.elevated({
@@ -96,8 +96,8 @@ class UFContainer extends StatelessWidget {
this.constraints,
this.gradient,
this.border,
}) : borderRadius = SpacingTokens.radiusLg,
boxShadow = ShadowTokens.sm;
}) : borderRadius = SpacingTokens.radiusMd,
boxShadow = null;
/// Container circulaire
const UFContainer.circular({

View File

@@ -28,13 +28,12 @@ class UFHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.xl),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.primaryGreen, AppColors.brandGreenLight],
),
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
boxShadow: ShadowTokens.primary,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Row(
children: [
@@ -43,12 +42,12 @@ class UFHeader extends StatelessWidget {
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
),
child: Icon(
icon,
color: Colors.white,
size: 24,
size: 18,
),
),
const SizedBox(width: SpacingTokens.lg),
@@ -95,7 +94,7 @@ class UFHeader extends StatelessWidget {
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
borderRadius: BorderRadius.circular(SpacingTokens.radiusXs),
),
child: IconButton(
onPressed: onNotificationTap,
@@ -111,7 +110,7 @@ class UFHeader extends StatelessWidget {
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
borderRadius: BorderRadius.circular(SpacingTokens.radiusXs),
),
child: IconButton(
onPressed: onSettingsTap,

View File

@@ -34,6 +34,7 @@ class UFPageHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
return Column(
@@ -59,30 +60,30 @@ class UFPageHeader extends StatelessWidget {
),
),
const SizedBox(width: SpacingTokens.md),
// Titre
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
fontWeight: FontWeight.w600,
),
),
),
// Actions
if (actions != null) ...actions!,
],
),
),
// Divider optionnel
if (showDivider)
const Divider(
Divider(
height: 1,
thickness: 1,
color: AppColors.lightBorder,
color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
),
],
);
@@ -110,6 +111,7 @@ class UFPageHeaderWithStats extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
return Column(
@@ -138,18 +140,18 @@ class UFPageHeaderWithStats extends StatelessWidget {
),
),
const SizedBox(width: SpacingTokens.md),
// Titre
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
fontWeight: FontWeight.w600,
),
),
),
// Actions
if (actions != null) ...actions!,
],
@@ -172,7 +174,7 @@ class UFPageHeaderWithStats extends StatelessWidget {
padding: EdgeInsets.only(
right: isLast ? 0 : SpacingTokens.sm,
),
child: _buildStatItem(stat),
child: _buildStatItem(stat, isDark),
),
);
}).toList(),
@@ -180,16 +182,16 @@ class UFPageHeaderWithStats extends StatelessWidget {
),
// Divider
const Divider(
Divider(
height: 1,
thickness: 1,
color: AppColors.lightBorder,
color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
),
],
);
}
Widget _buildStatItem(UFHeaderStat stat) {
Widget _buildStatItem(UFHeaderStat stat, bool isDark) {
final effectiveColor = stat.color ?? AppColors.primaryGreen;
return UFContainer.rounded(
padding: const EdgeInsets.symmetric(
@@ -211,7 +213,7 @@ class UFPageHeaderWithStats extends StatelessWidget {
Text(
stat.label,
style: AppTypography.badgeText.copyWith(
color: AppColors.textSecondaryLight,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// En-tête de section dashboard — titre 13px w700 avec trailing optionnel
/// Style de référence : super_admin_dashboard._buildSectionHeader
class UFSectionHeader extends StatelessWidget {
final String title;
/// Texte secondaire affiché à droite : "· trailing"
final String? trailing;
const UFSectionHeader(this.title, {this.trailing, super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(
title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
if (trailing != null && trailing!.isNotEmpty) ...[
const SizedBox(width: 6),
Text(
'· $trailing',
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
],
);
}
}

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Bouton d'action rapide UnionFlow
/// Style fintech : fond blanc, icône + texte colorés, bordure grise légère
/// Copie exacte du style _buildActionCell du super_admin_dashboard
class UnionActionButton extends StatelessWidget {
final IconData icon;
final String label;
@@ -20,36 +22,36 @@ class UnionActionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
// backgroundColor sert d'accent (icône + texte), iconColor prend la priorité
final accentColor = iconColor ?? backgroundColor ?? UnionFlowColors.unionGreen;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 8),
decoration: BoxDecoration(
color: backgroundColor ?? UnionFlowColors.unionGreenPale,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: (backgroundColor ?? UnionFlowColors.unionGreenPale)
.withOpacity(0.2),
width: 1,
),
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: UnionFlowColors.border),
),
child: Column(
mainAxisSize: MainAxisSize.min,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 28,
color: iconColor ?? UnionFlowColors.unionGreen,
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
Icon(icon, size: 16, color: accentColor),
const SizedBox(width: 6),
Flexible(
child: Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: accentColor,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
textAlign: TextAlign.center,
),
],
),
@@ -73,7 +75,7 @@ class UnionActionGrid extends StatelessWidget {
children: [
for (int i = 0; i < actions.length; i++) ...[
Expanded(child: actions[i]),
if (i < actions.length - 1) const SizedBox(width: 12),
if (i < actions.length - 1) const SizedBox(width: 10),
],
],
);

View File

@@ -22,75 +22,87 @@ class UnionBalanceCard extends StatelessWidget {
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
// Bordure dorée subtile en haut
border: const Border(
top: BorderSide(
color: UnionFlowColors.gold,
width: 3,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
decoration: BoxDecoration(
color: UnionFlowColors.surface,
border: Border.all(color: UnionFlowColors.border),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label.toUpperCase(),
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
letterSpacing: 0.8,
),
),
const SizedBox(height: 8),
// Montant principal
Text(
amount,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
color: UnionFlowColors.unionGreen,
height: 1.2,
),
),
// Trend (optionnel)
if (trend != null) ...[
const SizedBox(height: 10),
Row(
children: [
Icon(
isTrendPositive == true
? Icons.trending_up
: Icons.trending_down,
size: 16,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
const SizedBox(width: 4),
Text(
trend!,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(height: 2, color: UnionFlowColors.gold),
Padding(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 12),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label.toUpperCase(),
style: const TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
letterSpacing: 0.8,
),
),
const SizedBox(height: 4),
Text(
amount,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
color: UnionFlowColors.textPrimary,
height: 1,
letterSpacing: -0.5,
),
),
],
),
),
),
],
if (trend != null)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
children: [
Icon(
isTrendPositive == true
? Icons.arrow_upward_rounded
: Icons.arrow_downward_rounded,
size: 11,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
const SizedBox(width: 2),
Text(
trend!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
),
],
),
const Text(
'ce mois',
style: TextStyle(fontSize: 9, color: UnionFlowColors.textTertiary),
),
],
),
],
),
),
],
],
),
),
),
);

View File

@@ -26,25 +26,18 @@ class UnionGlassCard extends StatelessWidget {
child: Container(
margin: margin,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius ?? 16),
borderRadius: BorderRadius.circular(borderRadius ?? 10),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: UnionFlowColors.unionGreen.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius ?? 16),
borderRadius: BorderRadius.circular(borderRadius ?? 10),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: padding ?? const EdgeInsets.all(20),
padding: padding ?? const EdgeInsets.all(10),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,

View File

@@ -77,11 +77,10 @@ class UnionLineChart extends StatelessWidget {
final effectiveGradientEnd = gradientEndColor ?? UnionFlowColors.unionGreen.withOpacity(0.0);
return Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -105,7 +104,7 @@ class UnionLineChart extends StatelessWidget {
),
),
],
const SizedBox(height: 20),
const SizedBox(height: 10),
// Chart
SizedBox(

View File

@@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../tokens/unionflow_colors.dart';
/// Graphique circulaire UnionFlow - Pour afficher des répartitions
/// Graphique circulaire UnionFlow — petit donut compact avec légende latérale
/// Layout horizontal : donut 80×80 à gauche + légende à droite
/// Style uniforme avec les autres cards du design system (border + borderRadius)
class UnionPieChart extends StatelessWidget {
final List<PieChartSectionData> sections;
final String title;
@@ -19,47 +21,110 @@ class UnionPieChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Forcer compact : radius 14, pas de titre dans le donut
final compactSections = sections
.map((s) => s.copyWith(radius: 14, showTitle: false))
.toList();
return Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: UnionFlowColors.border),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
Row(
children: [
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const Spacer(),
if (subtitle != null)
Text(
subtitle!,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textTertiary,
),
),
],
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
),
],
const SizedBox(height: 20),
const SizedBox(height: 10),
// Chart
SizedBox(
height: 180,
child: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: centerSpaceRadius ?? 50,
sections: sections,
// Donut + légende côte à côte
Row(
children: [
// Petit donut
SizedBox(
width: 80,
height: 80,
child: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: centerSpaceRadius ?? 22,
sections: compactSections,
),
),
),
),
const SizedBox(width: 14),
// Légende
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: sections.map((s) {
// Le title de la section peut contenir '\n' ex: '50%\nActifs'
final parts = s.title.split('\n');
final pct = parts.isNotEmpty ? parts[0] : '';
final label = parts.length > 1 ? parts[1] : s.title;
return Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: s.color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textSecondary,
),
overflow: TextOverflow.ellipsis,
),
),
Text(
pct,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: s.color,
),
),
],
),
);
}).toList(),
),
),
],
),
],
),
@@ -79,8 +144,9 @@ class UnionPieChartSection {
return PieChartSectionData(
color: color,
value: value,
title: showTitle ? title : '',
title: title,
radius: radius,
showTitle: showTitle,
titleStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,

View File

@@ -25,11 +25,10 @@ class UnionProgressCard extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -43,7 +42,7 @@ class UnionProgressCard extends StatelessWidget {
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 12),
const SizedBox(height: 8),
// Progress bar
Stack(
@@ -69,13 +68,6 @@ class UnionProgressCard extends StatelessWidget {
],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: effectiveColor.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
),
),

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Widget de statistique compacte avec icône et tendance
/// Widget de statistique compacte — style identique à _buildKpiCell du super admin
/// fond blanc, bordure gauche colorée, icône + valeur + label
/// [compact] réduit le padding vertical pour les grilles très plates
class UnionStatWidget extends StatelessWidget {
final String label;
final String value;
@@ -10,6 +12,9 @@ class UnionStatWidget extends StatelessWidget {
final String? trend;
final bool? isTrendUp;
/// Mode ultra-compact : padding vertical réduit, espacement minimal
final bool compact;
const UnionStatWidget({
super.key,
required this.label,
@@ -18,86 +23,65 @@ class UnionStatWidget extends StatelessWidget {
required this.color,
this.trend,
this.isTrendUp,
this.compact = false,
});
@override
Widget build(BuildContext context) {
final EdgeInsets pad = compact
? const EdgeInsets.symmetric(horizontal: 8, vertical: 5)
: const EdgeInsets.all(6);
return Container(
padding: const EdgeInsets.all(16),
padding: pad,
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
border: Border(
left: BorderSide(
color: color,
width: 4,
),
),
borderRadius: BorderRadius.circular(10),
border: Border(left: BorderSide(color: color, width: 3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icon
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 20, color: color),
),
const SizedBox(height: 12),
// Value
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: color,
),
),
const SizedBox(height: 4),
// Label
Text(
label,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
),
),
// Trend
if (trend != null) ...[
const SizedBox(height: 8),
Row(
children: [
Icon(
isTrendUp == true
? Icons.trending_up
: Icons.trending_down,
size: 14,
color: isTrendUp == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
const SizedBox(width: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, size: 13, color: color),
if (trend != null) ...[
const Spacer(),
Text(
trend!,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: isTrendUp == true
? UnionFlowColors.success
: UnionFlowColors.error,
fontSize: 8,
fontWeight: FontWeight.w700,
color: color,
),
),
],
],
),
SizedBox(height: compact ? 2 : 4),
Text(
value,
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w800,
color: color,
letterSpacing: -0.3,
height: 1,
),
],
),
SizedBox(height: compact ? 1 : 2),
Text(
label,
style: const TextStyle(
fontSize: 9,
fontWeight: FontWeight.w500,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);

View File

@@ -153,11 +153,10 @@ class UnionTransactionCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -35,14 +35,7 @@ class UnionUnifiedAccountCard extends StatelessWidget {
UnionFlowColors.unionGreen.withOpacity(0.85),
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: UnionFlowColors.unionGreen.withOpacity(0.35),
offset: const Offset(0, 10),
blurRadius: 20,
),
],
borderRadius: BorderRadius.circular(12),
),
child: Stack(
children: [
@@ -58,7 +51,7 @@ class UnionUnifiedAccountCard extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -95,7 +88,7 @@ class UnionUnifiedAccountCard extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white30),
),
child: Text(
@@ -111,8 +104,8 @@ class UnionUnifiedAccountCard extends StatelessWidget {
],
),
const SizedBox(height: 32),
const SizedBox(height: 16),
// Solde Total Disponible
const Text(
'Solde Total Disponible',
@@ -129,15 +122,15 @@ class UnionUnifiedAccountCard extends StatelessWidget {
soldeTotal,
style: const TextStyle(
color: Colors.white,
fontSize: 36,
fontSize: 24,
fontWeight: FontWeight.w800,
letterSpacing: -0.5,
),
),
),
const SizedBox(height: 32),
const SizedBox(height: 16),
// Grille de détails
Row(
children: [
@@ -147,8 +140,8 @@ class UnionUnifiedAccountCard extends StatelessWidget {
],
),
const SizedBox(height: 20),
const SizedBox(height: 10),
// Barre d'engagement
Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Carte identité utilisateur — header uniforme pour tous les dashboards
/// Gradient coloré + initiales + nom + sous-titre + badge rôle
class UserIdentityCard extends StatelessWidget {
final String initials;
final String name;
final String subtitle;
final String badgeLabel;
final Gradient gradient;
final Color accentColor;
/// true = fond clair → texte sombre, badge avec fond coloré
/// false (défaut) = fond sombre → texte blanc, badge blanc + texte coloré
final bool lightBackground;
/// Afficher la bordure supérieure colorée (accentColor)
final bool showTopBorder;
const UserIdentityCard({
super.key,
required this.initials,
required this.name,
required this.subtitle,
required this.badgeLabel,
required this.gradient,
required this.accentColor,
this.lightBackground = false,
this.showTopBorder = true,
});
@override
Widget build(BuildContext context) {
final textColor =
lightBackground ? UnionFlowColors.textPrimary : Colors.white;
final subtitleColor = lightBackground
? UnionFlowColors.textSecondary
: Colors.white.withOpacity(0.85);
final avatarBg = lightBackground
? accentColor.withOpacity(0.15)
: Colors.white.withOpacity(0.25);
final avatarTextColor = lightBackground ? accentColor : Colors.white;
final badgeBg = lightBackground ? accentColor : Colors.white;
final badgeTextColor = lightBackground ? Colors.white : accentColor;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
gradient: gradient,
borderRadius: BorderRadius.circular(10),
border: showTopBorder
? Border(top: BorderSide(color: accentColor, width: 3))
: null,
boxShadow: showTopBorder
? [
BoxShadow(
color: accentColor.withOpacity(0.25),
blurRadius: 10,
offset: const Offset(0, 3),
)
]
: null,
),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: avatarBg,
child: Text(
initials,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: avatarTextColor,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: textColor,
),
),
const SizedBox(height: 3),
Text(
subtitle,
style: TextStyle(fontSize: 11, color: subtitleColor),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: badgeBg,
borderRadius: BorderRadius.circular(4),
),
child: Text(
badgeLabel,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w800,
color: badgeTextColor,
letterSpacing: 0.8,
),
),
),
],
),
);
}
}