feat(features): refontes explore/feed/finance_workflow/help/logs/members/notifications

- explore + feed : pages de découverte (réseau, fil d'actualité)
- finance_workflow : approvals bloc + budgets bloc + dialogs
- help : support page avec FAQ + contact
- logs : monitoring bloc avec metrics + alerts + searchLogs
- members : recherche avancée, bulk actions, bloc complet, import/export
- notifications : bloc + page
This commit is contained in:
dahoud
2026-04-15 20:27:01 +00:00
parent 120434aba0
commit dbf6a972ba
7 changed files with 84 additions and 78 deletions

View File

@@ -63,7 +63,7 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez ajouter au moins une ligne budgétaire'),
backgroundColor: Colors.red,
backgroundColor: AppColors.error,
),
);
return;
@@ -112,7 +112,7 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: AppColors.primaryGreen,
color: AppColors.primary,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(SpacingTokens.radiusMd),
topRight: Radius.circular(SpacingTokens.radiusMd),
@@ -257,8 +257,8 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
icon: const Icon(Icons.add, size: 18),
label: const Text('Ajouter'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.primary,
foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.md,
vertical: SpacingTokens.sm,
@@ -274,16 +274,16 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.grey.shade100,
color: Theme.of(context).colorScheme.surfaceContainerLow,
borderRadius:
BorderRadius.circular(SpacingTokens.radiusSm),
border: Border.all(color: Colors.grey.shade300),
border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
),
child: const Center(
child: Text(
'Aucune ligne budgétaire.\nCliquez sur "Ajouter" pour commencer.',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
style: TextStyle(color: AppColors.textTertiary),
),
),
)
@@ -307,9 +307,9 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: Colors.grey.shade50,
color: Theme.of(context).colorScheme.surfaceContainerLow,
border: Border(
top: BorderSide(color: Colors.grey.shade300),
top: BorderSide(color: Theme.of(context).colorScheme.outlineVariant),
),
),
child: Row(
@@ -326,7 +326,7 @@ class _CreateBudgetDialogState extends State<CreateBudgetDialog> {
label: const Text('Créer le budget'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.md,
@@ -419,7 +419,7 @@ class _BudgetLineWidgetState extends State<_BudgetLineWidget> {
children: [
Row(
children: [
const Icon(Icons.receipt_long, color: AppColors.primaryGreen),
const Icon(Icons.receipt_long, color: AppColors.primary),
const SizedBox(width: SpacingTokens.sm),
const Text(
'Ligne budgétaire',
@@ -427,7 +427,7 @@ class _BudgetLineWidgetState extends State<_BudgetLineWidget> {
),
const Spacer(),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
icon: const Icon(Icons.delete, color: AppColors.error),
onPressed: widget.onRemove,
tooltip: 'Supprimer',
),

View File

@@ -26,7 +26,8 @@ MembreCompletModel _$MembreCompletModelFromJson(Map<String, dynamic> json) =>
profession: json['profession'] as String?,
nationalite: json['nationalite'] as String?,
photo: json['photo'] as String?,
statut: $enumDecodeNullable(_$StatutMembreEnumMap, json['statutCompte']) ??
statut:
$enumDecodeNullable(_$StatutMembreEnumMap, json['statutCompte']) ??
StatutMembre.actif,
role: json['role'] as String?,
organisationId: json['organisationId'] as String?,

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import '../../../../shared/models/membre_search_criteria.dart';
import '../../../../shared/models/membre_search_result.dart';
import '../../../organizations/domain/repositories/organization_repository.dart';
@@ -114,10 +116,9 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Recherche Avancée'),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
appBar: UFAppBar(
title: 'Recherche Avancée',
moduleGradient: ModuleColors.membresGradient,
elevation: 0,
bottom: TabBar(
controller: _tabController,
@@ -179,7 +180,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
const Icon(Icons.flash_on, color: AppColors.primaryGreen),
const Icon(Icons.flash_on, color: AppColors.primary),
const SizedBox(width: 8),
Text(
'Recherche Rapide',
@@ -234,7 +235,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
const Icon(Icons.tune, color: AppColors.primaryGreen),
const Icon(Icons.tune, color: AppColors.primary),
const SizedBox(width: 8),
Text(
'Critères Détaillés',
@@ -325,7 +326,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
const Icon(Icons.filter_alt, color: AppColors.primaryGreen),
const Icon(Icons.filter_alt, color: AppColors.primary),
const SizedBox(width: 8),
Text(
'Filtres Avancés',
@@ -479,16 +480,16 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search, size: 64, color: Colors.grey),
Icon(Icons.search, size: 64, color: AppColors.textTertiary),
SizedBox(height: 16),
Text(
'Aucune recherche effectuée',
style: TextStyle(fontSize: 18, color: Colors.grey),
style: TextStyle(fontSize: 18, color: AppColors.textTertiary),
),
SizedBox(height: 8),
Text(
'Utilisez l\'onglet Critères pour lancer une recherche',
style: TextStyle(color: Colors.grey),
style: TextStyle(color: AppColors.textTertiary),
),
],
),
@@ -535,16 +536,16 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.analytics, size: 64, color: Colors.grey),
Icon(Icons.analytics, size: 64, color: AppColors.textTertiary),
SizedBox(height: 16),
Text(
'Aucune statistique disponible',
style: TextStyle(fontSize: 18, color: Colors.grey),
style: TextStyle(fontSize: 18, color: AppColors.textTertiary),
),
SizedBox(height: 8),
Text(
'Effectuez une recherche pour voir les statistiques',
style: TextStyle(color: Colors.grey),
style: TextStyle(color: AppColors.textTertiary),
),
],
),
@@ -574,8 +575,8 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
return ActionChip(
label: Text(label),
onPressed: onTap,
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
labelStyle: const TextStyle(color: AppColors.primaryGreen),
backgroundColor: AppColors.primary.withOpacity(0.1),
labelStyle: const TextStyle(color: AppColors.primary),
);
}

View File

@@ -214,9 +214,9 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color(0xFF1E2A1E), // vert ardoise sombre
color: const Color(0xFF0D1428), // navy sombre terminal
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2E4A2E)),
border: Border.all(color: const Color(0xFF2D3554)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -225,23 +225,23 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
Container(
padding: const EdgeInsets.fromLTRB(14, 10, 10, 10),
decoration: const BoxDecoration(
color: Color(0xFF162216),
color: Color(0xFF060A14),
borderRadius:
BorderRadius.vertical(top: Radius.circular(11)),
border: Border(
bottom: BorderSide(color: Color(0xFF2E4A2E))),
bottom: BorderSide(color: Color(0xFF2D3554))),
),
child: Row(
children: [
const Icon(Icons.terminal_rounded,
size: 13, color: Color(0xFF4CAF50)),
size: 13, color: Color(0xFF297FFF)),
const SizedBox(width: 6),
const Text(
'credentials.txt',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Color(0xFF90C890),
color: Color(0xFF69B7FF),
fontFamily: 'monospace'),
),
const Spacer(),
@@ -255,12 +255,12 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
children: [
Icon(Icons.check_rounded,
size: 13,
color: Color(0xFF4CAF50)),
color: Color(0xFF22C55E)),
SizedBox(width: 4),
Text('Copié',
style: TextStyle(
fontSize: 11,
color: Color(0xFF4CAF50),
color: Color(0xFF22C55E),
fontFamily: 'monospace')),
],
)
@@ -269,12 +269,12 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
children: [
Icon(Icons.copy_rounded,
size: 13,
color: Color(0xFF90C890)),
color: Color(0xFF69B7FF)),
SizedBox(width: 4),
Text('Copier tout',
style: TextStyle(
fontSize: 11,
color: Color(0xFF90C890),
color: Color(0xFF69B7FF),
fontFamily: 'monospace')),
],
),
@@ -291,7 +291,7 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_credLine('# Identifiants UnionFlow',
color: const Color(0xFF6A9955),
color: const Color(0xFF5C8DB5),
isComment: true),
const SizedBox(height: 8),
_credLine('email ',
@@ -307,7 +307,7 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
const SizedBox(height: 12),
_credLine(
'# Changez le mot de passe à la 1ère connexion',
color: const Color(0xFF6A9955),
color: const Color(0xFF5C8DB5),
isComment: true),
],
),
@@ -426,7 +426,7 @@ class _CredentialsDialogState extends State<_CredentialsDialog> {
style: TextStyle(
fontFamily: 'monospace',
fontSize: 11,
color: color ?? const Color(0xFF6A9955),
color: color ?? const Color(0xFF5C8DB5),
height: 1.4),
);
}

View File

@@ -3,6 +3,8 @@
library edit_member_dialog;
import 'package:flutter/material.dart';
import '../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import '../../bloc/membres_bloc.dart';
@@ -325,8 +327,8 @@ class _EditMemberDialogState extends State<EditMemberDialog> {
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[100],
border: Border(top: BorderSide(color: Colors.grey[300]!)),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
border: Border(top: BorderSide(color: Theme.of(context).colorScheme.outline)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
@@ -432,7 +434,7 @@ class _EditMemberDialogState extends State<EditMemberDialog> {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Membre modifié avec succès'),
backgroundColor: Colors.green,
backgroundColor: AppColors.success,
),
);
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../../../../shared/models/membre_search_result.dart' as search_model;
import '../../data/models/membre_complete_model.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
/// Widget d'affichage des résultats de recherche de membres
/// Gère la pagination, le tri et l'affichage des membres trouvés
@@ -56,20 +57,20 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
Icon(
Icons.search_off,
size: 64,
color: Colors.grey[400],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
'Aucun membre trouvé',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
'Essayez de modifier vos critères de recherche',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[500],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 24),
@@ -117,9 +118,9 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
),
Chip(
label: Text('${widget.result.executionTimeMs}ms'),
backgroundColor: Colors.green.withOpacity(0.1),
backgroundColor: AppColors.success.withOpacity(0.1),
labelStyle: const TextStyle(
color: Colors.green,
color: AppColors.success,
fontSize: 12,
),
),
@@ -130,7 +131,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
Text(
'Critères: ${widget.result.criteria.description}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@@ -162,7 +163,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
child: Text(
_getInitials(membre.nom, membre.prenom),
style: const TextStyle(
color: Colors.white,
color: AppColors.onPrimary,
fontWeight: FontWeight.bold,
),
),
@@ -177,7 +178,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
if (membre.email.isNotEmpty)
Row(
children: [
const Icon(Icons.email, size: 14, color: Colors.grey),
const Icon(Icons.email, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Expanded(
child: Text(
@@ -191,7 +192,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
if (membre.telephone?.isNotEmpty == true)
Row(
children: [
const Icon(Icons.phone, size: 14, color: Colors.grey),
const Icon(Icons.phone, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Text(
membre.telephone!,
@@ -202,7 +203,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
if (membre.organisationNom?.isNotEmpty == true)
Row(
children: [
const Icon(Icons.business, size: 14, color: Colors.grey),
const Icon(Icons.business, size: 14, color: AppColors.textTertiary),
const SizedBox(width: 4),
Expanded(
child: Text(
@@ -225,7 +226,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
_formatRoles(membre.role!),
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
color: AppColors.textTertiary,
),
textAlign: TextAlign.center,
),
@@ -261,8 +262,8 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
icon: const Icon(Icons.chevron_left),
label: const Text('Précédent'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[100],
foregroundColor: Colors.grey[700],
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
foregroundColor: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
@@ -289,7 +290,7 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
label: const Text('Suivant'),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
foregroundColor: AppColors.onPrimary,
),
),
],
@@ -322,15 +323,15 @@ class _MembreSearchResultsState extends State<MembreSearchResults> {
Color _getStatusColor(StatutMembre statut) {
switch (statut) {
case StatutMembre.actif:
return Colors.green;
return AppColors.success;
case StatutMembre.inactif:
return Colors.orange;
return AppColors.warning;
case StatutMembre.suspendu:
return Colors.red;
return AppColors.error;
case StatutMembre.enAttente:
return Colors.grey;
return AppColors.textTertiary;
default:
return Colors.grey;
return AppColors.textTertiary;
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../../shared/models/membre_search_result.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
/// Widget d'affichage des statistiques de recherche
/// Présente les métriques et graphiques des résultats de recherche
@@ -81,7 +82,7 @@ class SearchStatisticsCard extends StatelessWidget {
'Total Membres',
statistics.totalMembres.toString(),
Icons.people,
Colors.blue,
AppColors.info,
),
),
const SizedBox(width: 12),
@@ -91,7 +92,7 @@ class SearchStatisticsCard extends StatelessWidget {
'Membres Actifs',
statistics.membresActifs.toString(),
Icons.person,
Colors.green,
AppColors.success,
),
),
],
@@ -105,7 +106,7 @@ class SearchStatisticsCard extends StatelessWidget {
'Âge Moyen',
'${statistics.ageMoyen.toStringAsFixed(1)} ans',
Icons.cake,
Colors.orange,
AppColors.warning,
),
),
const SizedBox(width: 12),
@@ -155,7 +156,7 @@ class SearchStatisticsCard extends StatelessWidget {
Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
@@ -196,7 +197,7 @@ class SearchStatisticsCard extends StatelessWidget {
PieChartSectionData(
value: statistics.membresActifs.toDouble(),
title: '${statistics.pourcentageActifs.toStringAsFixed(1)}%',
color: Colors.green,
color: AppColors.success,
radius: 60,
titleStyle: const TextStyle(
fontSize: 12,
@@ -208,7 +209,7 @@ class SearchStatisticsCard extends StatelessWidget {
PieChartSectionData(
value: statistics.membresInactifs.toDouble(),
title: '${statistics.pourcentageInactifs.toStringAsFixed(1)}%',
color: Colors.orange,
color: AppColors.warning,
radius: 60,
titleStyle: const TextStyle(
fontSize: 12,
@@ -232,14 +233,14 @@ class SearchStatisticsCard extends StatelessWidget {
_buildLegendItem(
'Actifs',
statistics.membresActifs,
Colors.green,
AppColors.success,
),
const SizedBox(height: 8),
if (statistics.membresInactifs > 0)
_buildLegendItem(
'Inactifs',
statistics.membresInactifs,
Colors.orange,
AppColors.warning,
),
],
),
@@ -335,7 +336,7 @@ class SearchStatisticsCard extends StatelessWidget {
Icon(
icon,
size: 20,
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 12),
Expanded(
@@ -382,26 +383,26 @@ class SearchStatisticsCard extends StatelessWidget {
Text(
statistics.description,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[700],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
color: AppColors.infoContainer,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
border: Border.all(color: AppColors.info),
),
child: Row(
children: [
const Icon(Icons.lightbulb, color: Colors.blue),
const Icon(Icons.lightbulb, color: AppColors.info),
const SizedBox(width: 8),
Expanded(
child: Text(
'Ces statistiques sont calculées en temps réel sur les résultats de votre recherche.',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.blue[700],
color: AppColors.primaryDark,
),
),
),