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

@@ -6,6 +6,8 @@ import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'membres_event.dart';
import 'membres_state.dart';
import '../../../shared/models/membre_search_criteria.dart';
import '../../../shared/models/membre_search_result.dart';
import '../domain/usecases/get_members.dart';
import '../domain/usecases/get_member_by_id.dart';
import '../domain/usecases/create_member.dart' as uc;
@@ -64,11 +66,25 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(const MembresLoading());
}
final result = await _getMembers(
page: event.page,
size: event.size,
recherche: event.recherche,
);
final MembreSearchResult result;
if (event.organisationId != null) {
// OrgAdmin : scope la requête à son organisation via la recherche avancée
result = await _searchMembers(
criteria: MembreSearchCriteria(
organisationIds: [event.organisationId!],
query: event.recherche?.isNotEmpty == true ? event.recherche : null,
),
page: event.page,
size: event.size,
);
} else {
// SuperAdmin et autres rôles : accès global sans filtre org
result = await _getMembers(
page: event.page,
size: event.size,
recherche: event.recherche,
);
}
emit(MembresLoaded(
membres: result.membres,
@@ -76,16 +92,19 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
currentPage: result.currentPage,
pageSize: result.pageSize,
totalPages: result.totalPages,
organisationId: event.organisationId,
));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur inattendue lors du chargement des membres: $e',
message: 'Erreur lors du chargement des membres. Veuillez réessayer.',
error: e,
));
}
@@ -110,14 +129,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
));
}
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors du chargement du membre: $e',
message: 'Erreur lors du chargement du membre. Veuillez réessayer.',
error: e,
));
}
@@ -135,6 +156,7 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembreCreated(membre));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
if (e.response?.statusCode == 400) {
// Erreur de validation
final errors = _extractValidationErrors(e.response?.data);
@@ -151,8 +173,9 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
));
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de la création du membre: $e',
message: 'Erreur lors de la création du membre. Veuillez réessayer.',
error: e,
));
}
@@ -170,6 +193,7 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembreUpdated(membre));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
if (e.response?.statusCode == 400) {
final errors = _extractValidationErrors(e.response?.data);
emit(MembresValidationError(
@@ -185,8 +209,9 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
));
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de la mise à jour du membre: $e',
message: 'Erreur lors de la mise à jour du membre. Veuillez réessayer.',
error: e,
));
}
@@ -204,14 +229,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembreDeleted(event.id));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de la suppression du membre: $e',
message: 'Erreur lors de la suppression du membre. Veuillez réessayer.',
error: e,
));
}
@@ -229,14 +256,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembreActivated(membre));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de l\'activation du membre: $e',
message: 'Erreur lors de l\'activation du membre. Veuillez réessayer.',
error: e,
));
}
@@ -254,14 +283,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembreDeactivated(membre));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de la désactivation du membre: $e',
message: 'Erreur lors de la désactivation du membre. Veuillez réessayer.',
error: e,
));
}
@@ -289,14 +320,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
totalPages: result.totalPages,
));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors de la recherche de membres: $e',
message: 'Erreur lors de la recherche de membres. Veuillez réessayer.',
error: e,
));
}
@@ -323,14 +356,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
totalPages: result.totalPages,
));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors du chargement des membres actifs: $e',
message: 'Erreur lors du chargement des membres actifs. Veuillez réessayer.',
error: e,
));
}
@@ -357,14 +392,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
totalPages: result.totalPages,
));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors du chargement des membres du bureau: $e',
message: 'Erreur lors du chargement des membres du bureau. Veuillez réessayer.',
error: e,
));
}
@@ -382,14 +419,16 @@ class MembresBloc extends Bloc<MembresEvent, MembresState> {
emit(MembresStatsLoaded(stats));
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) return;
emit(MembresNetworkError(
message: _getNetworkErrorMessage(e),
code: e.response?.statusCode.toString(),
error: e,
));
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) return;
emit(MembresError(
message: 'Erreur lors du chargement des statistiques: $e',
message: 'Erreur lors du chargement des statistiques. Veuillez réessayer.',
error: e,
));
}

View File

@@ -19,16 +19,18 @@ class LoadMembres extends MembresEvent {
final int size;
final String? recherche;
final bool refresh;
final String? organisationId;
const LoadMembres({
this.page = 0,
this.size = 20,
this.recherche,
this.refresh = false,
this.organisationId,
});
@override
List<Object?> get props => [page, size, recherche, refresh];
List<Object?> get props => [page, size, recherche, refresh, organisationId];
}
/// Événement pour charger un membre par ID

View File

@@ -40,6 +40,7 @@ class MembresLoaded extends MembresState {
final int pageSize;
final int totalPages;
final bool hasMore;
final String? organisationId;
const MembresLoaded({
required this.membres,
@@ -47,10 +48,19 @@ class MembresLoaded extends MembresState {
this.currentPage = 0,
this.pageSize = 20,
required this.totalPages,
this.organisationId,
}) : hasMore = currentPage < totalPages - 1;
@override
List<Object?> get props => [membres, totalElements, currentPage, pageSize, totalPages, hasMore];
List<Object?> get props => [
membres,
totalElements,
currentPage,
pageSize,
totalPages,
hasMore,
organisationId,
];
MembresLoaded copyWith({
List<MembreCompletModel>? membres,
@@ -58,6 +68,7 @@ class MembresLoaded extends MembresState {
int? currentPage,
int? pageSize,
int? totalPages,
String? organisationId,
}) {
return MembresLoaded(
membres: membres ?? this.membres,
@@ -65,6 +76,7 @@ class MembresLoaded extends MembresState {
currentPage: currentPage ?? this.currentPage,
pageSize: pageSize ?? this.pageSize,
totalPages: totalPages ?? this.totalPages,
organisationId: organisationId ?? this.organisationId,
);
}
}

View File

@@ -174,6 +174,10 @@ class MembreCompletModel extends Equatable {
@JsonKey(name: 'dateVerificationIdentite')
final DateTime? dateVerificationIdentite;
/// Mot de passe temporaire (retourné une seule fois à la création, null sinon)
@JsonKey(name: 'motDePasseTemporaire')
final String? motDePasseTemporaire;
const MembreCompletModel({
this.id,
required this.nom,
@@ -210,6 +214,7 @@ class MembreCompletModel extends Equatable {
this.niveauVigilanceKyc,
this.statutKyc,
this.dateVerificationIdentite,
this.motDePasseTemporaire,
});
/// Création depuis JSON
@@ -256,6 +261,7 @@ class MembreCompletModel extends Equatable {
NiveauVigilanceKyc? niveauVigilanceKyc,
StatutKyc? statutKyc,
DateTime? dateVerificationIdentite,
String? motDePasseTemporaire,
}) {
return MembreCompletModel(
id: id ?? this.id,
@@ -293,6 +299,7 @@ class MembreCompletModel extends Equatable {
niveauVigilanceKyc: niveauVigilanceKyc ?? this.niveauVigilanceKyc,
statutKyc: statutKyc ?? this.statutKyc,
dateVerificationIdentite: dateVerificationIdentite ?? this.dateVerificationIdentite,
motDePasseTemporaire: motDePasseTemporaire ?? this.motDePasseTemporaire,
);
}

View File

@@ -60,6 +60,7 @@ MembreCompletModel _$MembreCompletModelFromJson(Map<String, dynamic> json) =>
dateVerificationIdentite: json['dateVerificationIdentite'] == null
? null
: DateTime.parse(json['dateVerificationIdentite'] as String),
motDePasseTemporaire: json['motDePasseTemporaire'] as String?,
);
Map<String, dynamic> _$MembreCompletModelToJson(MembreCompletModel instance) =>
@@ -101,6 +102,7 @@ Map<String, dynamic> _$MembreCompletModelToJson(MembreCompletModel instance) =>
'statutKyc': _$StatutKycEnumMap[instance.statutKyc],
'dateVerificationIdentite':
instance.dateVerificationIdentite?.toIso8601String(),
'motDePasseTemporaire': instance.motDePasseTemporaire,
};
const _$GenreEnumMap = {

View File

@@ -49,10 +49,10 @@ class MembreRepositoryImpl implements IMembreRepository {
);
return _parseMembreSearchResult(response, page, size, const MembreSearchCriteria());
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des membres: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des membres: $e');
rethrow;
}
}
@@ -64,12 +64,29 @@ class MembreRepositoryImpl implements IMembreRepository {
if (map.containsKey('organisationId') && map['organisationId'] != null && map['organisationId'] is! String) {
map['organisationId'] = map['organisationId'].toString();
}
// Mapping statutCompte → statut avec normalisation des valeurs backend
if (map.containsKey('statutCompte') && !map.containsKey('statut')) {
map['statut'] = map['statutCompte'];
final sc = (map['statutCompte'] as String? ?? '').toUpperCase();
if (sc == 'ACTIF') {
map['statut'] = 'ACTIF';
} else if (sc == 'INACTIF') {
map['statut'] = 'INACTIF';
} else if (sc == 'SUSPENDU') {
map['statut'] = 'SUSPENDU';
} else {
map['statut'] = 'EN_ATTENTE';
}
}
if (map.containsKey('photoUrl') && !map.containsKey('photo')) {
map['photo'] = map['photoUrl'];
}
// roles (List) → role (premier rôle)
if (map.containsKey('roles') && !map.containsKey('role')) {
final roles = map['roles'];
if (roles is List && roles.isNotEmpty) {
map['role'] = roles.first?.toString();
}
}
if (map['id'] != null && map['id'] is! String) {
map['id'] = map['id'].toString();
}
@@ -155,12 +172,13 @@ class MembreRepositoryImpl implements IMembreRepository {
throw Exception('Erreur lors de la récupération du membre: ${response.statusCode}');
}
} on DioException catch (e) {
if (e.type == DioExceptionType.cancel) rethrow;
if (e.response?.statusCode == 404) {
return null;
}
throw Exception('Erreur réseau lors de la récupération du membre: ${e.message}');
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération du membre: $e');
rethrow;
}
}
@@ -177,10 +195,10 @@ class MembreRepositoryImpl implements IMembreRepository {
} else {
throw Exception('Erreur lors de la création du membre: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la création du membre: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la création du membre: $e');
rethrow;
}
}
@@ -197,10 +215,10 @@ class MembreRepositoryImpl implements IMembreRepository {
} else {
throw Exception('Erreur lors de la mise à jour du membre: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la mise à jour du membre: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la mise à jour du membre: $e');
rethrow;
}
}
@@ -212,10 +230,10 @@ class MembreRepositoryImpl implements IMembreRepository {
if (response.statusCode != 204 && response.statusCode != 200) {
throw Exception('Erreur lors de la suppression du membre: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la suppression du membre: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la suppression du membre: $e');
rethrow;
}
}
@@ -229,10 +247,10 @@ class MembreRepositoryImpl implements IMembreRepository {
} else {
throw Exception('Erreur lors de l\'activation du membre: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de l\'activation du membre: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de l\'activation du membre: $e');
rethrow;
}
}
@@ -246,10 +264,10 @@ class MembreRepositoryImpl implements IMembreRepository {
} else {
throw Exception('Erreur lors de la désactivation du membre: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la désactivation du membre: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la désactivation du membre: $e');
rethrow;
}
}
@@ -272,10 +290,10 @@ class MembreRepositoryImpl implements IMembreRepository {
);
return _parseMembreSearchResult(response, page, size, criteria);
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la recherche de membres: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la recherche de membres: $e');
rethrow;
}
}
@@ -315,10 +333,10 @@ class MembreRepositoryImpl implements IMembreRepository {
} else {
throw Exception('Erreur lors de la récupération des statistiques: ${response.statusCode}');
}
} on DioException catch (e) {
throw Exception('Erreur réseau lors de la récupération des statistiques: ${e.message}');
} on DioException {
rethrow;
} catch (e) {
throw Exception('Erreur inattendue lors de la récupération des statistiques: $e');
rethrow;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/models/membre_search_criteria.dart';
import '../../../../shared/models/membre_search_result.dart';
import '../../../organizations/domain/repositories/organization_repository.dart';
@@ -115,7 +116,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
return Scaffold(
appBar: AppBar(
title: const Text('Recherche Avancée'),
backgroundColor: Theme.of(context).primaryColor,
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
elevation: 0,
bottom: TabBar(
@@ -178,7 +179,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
Icon(Icons.flash_on, color: Theme.of(context).primaryColor),
const Icon(Icons.flash_on, color: AppColors.primaryGreen),
const SizedBox(width: 8),
Text(
'Recherche Rapide',
@@ -233,7 +234,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
Icon(Icons.tune, color: Theme.of(context).primaryColor),
const Icon(Icons.tune, color: AppColors.primaryGreen),
const SizedBox(width: 8),
Text(
'Critères Détaillés',
@@ -324,7 +325,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
children: [
Row(
children: [
Icon(Icons.filter_alt, color: Theme.of(context).primaryColor),
const Icon(Icons.filter_alt, color: AppColors.primaryGreen),
const SizedBox(width: 8),
Text(
'Filtres Avancés',
@@ -499,7 +500,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, size: 64, color: Colors.red),
const Icon(Icons.error, size: 64, color: AppColors.error),
const SizedBox(height: 16),
Text(
'Erreur de recherche',
@@ -509,7 +510,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
Text(
_errorMessage!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
style: const TextStyle(color: AppColors.error),
),
const SizedBox(height: 16),
ElevatedButton(
@@ -573,8 +574,8 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
return ActionChip(
label: Text(label),
onPressed: onTap,
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
labelStyle: TextStyle(color: Theme.of(context).primaryColor),
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
labelStyle: const TextStyle(color: AppColors.primaryGreen),
);
}
@@ -620,7 +621,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez spécifier au moins un critère de recherche'),
backgroundColor: Colors.orange,
backgroundColor: AppColors.warning,
),
);
return;
@@ -649,7 +650,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(result.resultDescription),
backgroundColor: Colors.green,
backgroundColor: AppColors.success,
),
);
} catch (e) {
@@ -661,7 +662,7 @@ class _AdvancedSearchPageState extends State<AdvancedSearchPage>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur de recherche: $e'),
backgroundColor: Colors.red,
backgroundColor: AppColors.error,
),
);
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../../../features/authentication/data/models/user_role.dart';
@@ -174,13 +175,13 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
builder: (context, state) {
if (state is! AuthAuthenticated) {
return Container(
color: const Color(0xFFF8F9FA),
color: AppColors.lightBackground,
child: const Center(child: CircularProgressIndicator()),
);
}
return Container(
color: const Color(0xFFF8F9FA),
color: AppColors.lightBackground,
child: _buildMembersContent(state),
);
},
@@ -196,19 +197,19 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
children: [
// Header avec titre et actions
_buildMembersHeader(state),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Statistiques et métriques
_buildMembersMetrics(),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Barre de recherche et filtres
_buildSearchAndFilters(),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Onglets de catégories
_buildCategoryTabs(),
const SizedBox(height: 16),
const SizedBox(height: 8),
// Liste/Grille des membres
_buildMembersDisplay(),
@@ -265,8 +266,8 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
'Métriques & Statistiques',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF6C5CE7),
fontSize: 18,
color: AppColors.primaryGreen,
fontSize: 14,
),
),
const SizedBox(height: 12),
@@ -280,7 +281,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
totalMembers.toString(),
'+$newThisMonth ce mois',
Icons.people,
const Color(0xFF6C5CE7),
AppColors.primaryGreen,
trend: newThisMonth > 0 ? 'up' : 'stable',
),
),
@@ -291,7 +292,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
activeMembers.toString(),
'${((activeMembers / totalMembers) * 100).toStringAsFixed(1)}%',
Icons.check_circle,
const Color(0xFF00B894),
AppColors.success,
trend: 'up',
),
),
@@ -308,7 +309,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
avgContribution.toStringAsFixed(0),
'Contribution',
Icons.trending_up,
const Color(0xFF0984E3),
AppColors.brandGreenLight,
trend: 'up',
),
),
@@ -319,7 +320,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
newThisMonth.toString(),
'Ce mois',
Icons.new_releases,
const Color(0xFFF39C12),
AppColors.warning,
trend: newThisMonth > 0 ? 'up' : 'stable',
),
),
@@ -388,7 +389,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
),
const SizedBox(height: 2),
@@ -396,7 +397,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
subtitle,
style: const TextStyle(
fontSize: 10,
color: Color(0xFF9CA3AF),
color: AppColors.textSecondaryLight,
),
),
],
@@ -424,7 +425,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
),
child: Row(
children: [
const Icon(Icons.search, color: Color(0xFF6B7280)),
const Icon(Icons.search, color: AppColors.textSecondaryLight),
const SizedBox(width: 12),
Expanded(
child: TextField(
@@ -432,7 +433,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
decoration: const InputDecoration(
hintText: 'Rechercher par nom, email, département...',
border: InputBorder.none,
hintStyle: TextStyle(color: Color(0xFF9CA3AF)),
hintStyle: TextStyle(color: AppColors.textSecondaryLight),
),
onChanged: (value) {
setState(() {
@@ -449,13 +450,13 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
_searchQuery = '';
});
},
icon: const Icon(Icons.clear, color: Color(0xFF6B7280)),
icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight),
),
const SizedBox(width: 8),
Container(
height: 32,
width: 1,
color: const Color(0xFFE5E7EB),
color: AppColors.lightBorder,
),
const SizedBox(width: 8),
IconButton(
@@ -466,7 +467,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
},
icon: Icon(
_showAdvancedFilters ? Icons.filter_list_off : Icons.filter_list,
color: _showAdvancedFilters ? const Color(0xFF6C5CE7) : const Color(0xFF6B7280),
color: _showAdvancedFilters ? AppColors.primaryGreen : AppColors.textSecondaryLight,
),
tooltip: 'Filtres avancés',
),
@@ -478,7 +479,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
},
icon: Icon(
_isGridView ? Icons.view_list : Icons.grid_view,
color: const Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
tooltip: _isGridView ? 'Vue liste' : 'Vue grille',
),
@@ -526,14 +527,14 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
});
},
backgroundColor: Colors.white,
selectedColor: const Color(0xFF6C5CE7).withOpacity(0.1),
checkmarkColor: const Color(0xFF6C5CE7),
selectedColor: AppColors.primaryGreen.withOpacity(0.1),
checkmarkColor: AppColors.primaryGreen,
labelStyle: TextStyle(
color: isSelected ? const Color(0xFF6C5CE7) : const Color(0xFF6B7280),
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
side: BorderSide(
color: isSelected ? const Color(0xFF6C5CE7) : const Color(0xFFE5E7EB),
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
),
),
);
@@ -549,7 +550,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE5E7EB)),
border: Border.all(color: AppColors.lightBorder),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -558,7 +559,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
'Filtres Avancés',
style: TextStyle(
fontWeight: FontWeight.w600,
color: Color(0xFF374151),
color: AppColors.textPrimaryLight,
),
),
const SizedBox(height: 12),
@@ -589,14 +590,14 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
});
},
backgroundColor: Colors.grey[50],
selectedColor: const Color(0xFF6C5CE7).withOpacity(0.1),
checkmarkColor: const Color(0xFF6C5CE7),
selectedColor: AppColors.primaryGreen.withOpacity(0.1),
checkmarkColor: AppColors.primaryGreen,
labelStyle: TextStyle(
color: isSelected ? const Color(0xFF6C5CE7) : const Color(0xFF6B7280),
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
fontSize: 12,
),
side: BorderSide(
color: isSelected ? const Color(0xFF6C5CE7) : const Color(0xFFE5E7EB),
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
),
);
}).toList(),
@@ -616,14 +617,14 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
icon: const Icon(Icons.clear_all, size: 16),
label: const Text('Réinitialiser'),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF6B7280),
foregroundColor: AppColors.textSecondaryLight,
),
),
const Spacer(),
Text(
'${_getFilteredMembers().length} résultat(s)',
style: const TextStyle(
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
fontSize: 12,
),
),
@@ -656,9 +657,9 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Tab(text: 'Équipes', icon: Icon(Icons.groups, size: 18)),
Tab(text: 'Analytics', icon: Icon(Icons.analytics, size: 18)),
],
labelColor: const Color(0xFF6C5CE7),
unselectedLabelColor: const Color(0xFF6B7280),
indicatorColor: const Color(0xFF6C5CE7),
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
indicatorWeight: 3,
labelStyle: const TextStyle(
fontSize: 12,
@@ -804,7 +805,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Color(0xFF1F2937),
color: AppColors.textPrimaryLight,
),
),
),
@@ -829,7 +830,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Text(
member['email'],
style: const TextStyle(
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
fontSize: 14,
),
),
@@ -844,24 +845,24 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
const SizedBox(width: 4),
Text(
member['department'],
style: TextStyle(
style: const TextStyle(
fontSize: 12,
color: Colors.grey[600],
color: AppColors.textSecondaryLight,
),
),
const SizedBox(width: 12),
Icon(
const Icon(
Icons.location_on,
size: 12,
color: Colors.grey[500],
color: AppColors.textSecondaryLight,
),
const SizedBox(width: 4),
Expanded(
child: Text(
member['location'],
style: TextStyle(
style: const TextStyle(
fontSize: 12,
color: Colors.grey[600],
color: AppColors.textSecondaryLight,
),
overflow: TextOverflow.ellipsis,
),
@@ -903,7 +904,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
'Rejoint ${_formatDate(joinDate)}',
style: const TextStyle(
fontSize: 10,
color: Color(0xFF9CA3AF),
color: AppColors.textSecondaryLight,
),
),
const Spacer(),
@@ -911,7 +912,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
'Actif ${_formatRelativeTime(lastActivity)}',
style: const TextStyle(
fontSize: 10,
color: Color(0xFF9CA3AF),
color: AppColors.textSecondaryLight,
),
),
],
@@ -974,7 +975,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
child: const Icon(
Icons.more_vert,
size: 16,
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
),
),
@@ -1040,15 +1041,15 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Color _getStatusColor(String status) {
switch (status) {
case 'Actif':
return const Color(0xFF10B981);
return AppColors.success;
case 'Inactif':
return const Color(0xFF6B7280);
return AppColors.textSecondaryLight;
case 'Suspendu':
return const Color(0xFFDC2626);
return AppColors.error;
case 'En attente':
return const Color(0xFFF59E0B);
return AppColors.warning;
default:
return const Color(0xFF6B7280);
return AppColors.textSecondaryLight;
}
}
@@ -1056,30 +1057,30 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Color _getRoleColor(String role) {
switch (role) {
case 'Super Administrateur':
return const Color(0xFF7C3AED);
return AppColors.brandGreen;
case 'Administrateur Org':
return const Color(0xFF6366F1);
return AppColors.primaryGreen;
case 'Gestionnaire RH':
return const Color(0xFF0EA5E9);
return AppColors.info;
case 'Modérateur':
return const Color(0xFF059669);
return AppColors.brandGreenLight;
case 'Membre Actif':
return const Color(0xFF6C5CE7);
return AppColors.primaryGreen;
case 'Consultant':
return const Color(0xFFF59E0B);
return AppColors.warning;
case 'Membre Simple':
return const Color(0xFF6B7280);
return AppColors.textSecondaryLight;
default:
return const Color(0xFF6B7280);
return AppColors.textSecondaryLight;
}
}
/// Obtient la couleur selon le score de contribution
Color _getScoreColor(int score) {
if (score >= 90) return const Color(0xFF10B981);
if (score >= 70) return const Color(0xFF0EA5E9);
if (score >= 50) return const Color(0xFFF59E0B);
return const Color(0xFFDC2626);
if (score >= 90) return AppColors.success;
if (score >= 70) return AppColors.brandGreenLight;
if (score >= 50) return AppColors.warning;
return AppColors.error;
}
/// Formate une date
@@ -1174,7 +1175,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Messagerie groupée à venir. Utilisez l\'action « Message » sur un membre.'),
backgroundColor: Color(0xFF6C5CE7),
backgroundColor: AppColors.primaryGreen,
),
);
},
@@ -1190,7 +1191,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Export des membres en cours...'),
backgroundColor: Color(0xFF10B981),
backgroundColor: AppColors.success,
),
);
}
@@ -1234,7 +1235,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Message à ${member['name']} à implémenter'),
backgroundColor: const Color(0xFF0EA5E9),
backgroundColor: AppColors.info,
),
);
}
@@ -1257,7 +1258,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${member['name']} supprimé'),
backgroundColor: const Color(0xFFDC2626),
backgroundColor: AppColors.error,
),
);
},
@@ -1413,13 +1414,13 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
color: AppColors.primaryGreen.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.people_outline,
size: 48,
color: Color(0xFF6C5CE7),
color: AppColors.primaryGreen,
),
),
const SizedBox(height: 16),
@@ -1428,7 +1429,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF374151),
color: AppColors.textPrimaryLight,
),
),
const SizedBox(height: 8),
@@ -1437,7 +1438,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
? 'Aucun membre ne correspond à votre recherche'
: 'Aucun membre ne correspond aux filtres sélectionnés',
style: const TextStyle(
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
textAlign: TextAlign.center,
),
@@ -1454,7 +1455,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
icon: const Icon(Icons.refresh),
label: const Text('Réinitialiser les filtres'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C5CE7),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
),
),
@@ -1667,7 +1668,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
width: 24,
height: 24,
decoration: BoxDecoration(
color: index < 3 ? const Color(0xFFF59E0B) : const Color(0xFF6B7280),
color: index < 3 ? AppColors.warning : AppColors.textSecondaryLight,
borderRadius: BorderRadius.circular(12),
),
child: Center(
@@ -1707,7 +1708,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
member['role'],
style: const TextStyle(
fontSize: 12,
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
),
],
@@ -1890,7 +1891,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
icon: const Icon(Icons.edit),
label: const Text('Modifier'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C5CE7),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
),
),
@@ -1929,7 +1930,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF374151),
color: AppColors.textPrimaryLight,
),
),
const SizedBox(height: 12),
@@ -1948,7 +1949,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
Icon(
icon,
size: 20,
color: const Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
const SizedBox(width: 12),
Expanded(
@@ -1959,7 +1960,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
label,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF6B7280),
color: AppColors.textSecondaryLight,
),
),
Text(
@@ -1967,7 +1968,7 @@ class _MembersPageState extends State<MembersPage> with TickerProviderStateMixin
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF374151),
color: AppColors.textPrimaryLight,
),
),
],

View File

@@ -79,7 +79,7 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
final pendingCount = widget.members.where((m) => m['status'] == 'En attente').length;
return Container(
padding: const EdgeInsets.all(20),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
border: Border(
@@ -100,10 +100,10 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
Widget _buildStatBadge(String label, String value, Color color) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3), width: 1),
),
child: Column(
@@ -118,7 +118,7 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
Widget _buildSearchAndFilters() {
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
border: Border(bottom: BorderSide(color: UnionFlowColors.border.withOpacity(0.5), width: 1)),
@@ -192,10 +192,10 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
return GestureDetector(
onTap: () => setState(() => _filterStatus = label),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: isSelected ? UnionFlowColors.unionGreen : UnionFlowColors.surface,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: isSelected ? UnionFlowColors.unionGreen : UnionFlowColors.border, width: 1),
),
child: Text(
@@ -225,9 +225,9 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
onRefresh: () async => widget.onRefresh(),
color: UnionFlowColors.unionGreen,
child: ListView.separated(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
itemCount: filtered.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
separatorBuilder: (_, __) => const SizedBox(height: 6),
itemBuilder: (context, index) => _buildMemberCard(filtered[index]),
),
);
@@ -237,23 +237,22 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
return GestureDetector(
onTap: () => _showMemberDetails(member),
child: Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: UnionFlowColors.border.withOpacity(0.3), width: 1),
),
child: Row(
children: [
Container(
width: 48,
height: 48,
width: 32,
height: 32,
decoration: const BoxDecoration(gradient: UnionFlowColors.primaryGradient, shape: BoxShape.circle),
alignment: Alignment.center,
child: Text(
member['initiales'] ?? '??',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 18),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
),
),
const SizedBox(width: 12),
@@ -322,14 +321,14 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(color: UnionFlowColors.unionGreenPale, shape: BoxShape.circle),
child: const Icon(Icons.people_outline, size: 64, color: UnionFlowColors.unionGreen),
child: const Icon(Icons.people_outline, size: 40, color: UnionFlowColors.unionGreen),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
const Text(
'Aucun membre trouvé',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
const SizedBox(height: 8),
Text(
@@ -389,40 +388,40 @@ class _MembersPageWithDataAndPaginationState extends State<MembersPageWithDataAn
context: context,
backgroundColor: Colors.transparent,
builder: (context) => Container(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 80,
height: 80,
width: 56,
height: 56,
decoration: const BoxDecoration(gradient: UnionFlowColors.primaryGradient, shape: BoxShape.circle),
alignment: Alignment.center,
child: Text(
member['initiales'] ?? '??',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 32),
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 22),
),
),
const SizedBox(height: 16),
const SizedBox(height: 10),
Text(
member['name'] ?? '',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
const SizedBox(height: 4),
Text(
member['role'] ?? '',
style: const TextStyle(fontSize: 13, color: UnionFlowColors.textSecondary),
),
const SizedBox(height: 20),
const SizedBox(height: 12),
_buildInfoRow(Icons.email_outlined, member['email'] ?? 'Non fourni'),
_buildInfoRow(Icons.phone_outlined, member['phone'] ?? 'Non fourni'),
_buildInfoRow(Icons.location_on_outlined, member['location'] ?? 'Non renseigné'),
_buildInfoRow(Icons.work_outline, member['department'] ?? 'Aucun département'),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
),
),

View File

@@ -22,21 +22,23 @@ final _getIt = GetIt.instance;
/// Wrapper qui fournit le BLoC à la page des membres
class MembersPageWrapper extends StatelessWidget {
const MembersPageWrapper({super.key});
final String? organisationId;
const MembersPageWrapper({super.key, this.organisationId});
@override
Widget build(BuildContext context) {
AppLogger.info('MembersPageWrapper: Création du BlocProvider');
return BlocProvider<MembresBloc>(
create: (context) {
AppLogger.info('MembresPageWrapper: Initialisation du MembresBloc');
final bloc = _getIt<MembresBloc>();
// Charger les membres au démarrage
bloc.add(const LoadMembres());
bloc.add(LoadMembres(organisationId: organisationId));
return bloc;
},
child: const MembersPageConnected(),
child: MembersPageConnected(organisationId: organisationId),
);
}
}
@@ -45,15 +47,59 @@ class MembersPageWrapper extends StatelessWidget {
///
/// Cette page gère les états du BLoC et affiche l'UI appropriée
class MembersPageConnected extends StatelessWidget {
const MembersPageConnected({super.key});
final String? organisationId;
const MembersPageConnected({super.key, this.organisationId});
@override
Widget build(BuildContext context) {
return BlocListener<MembresBloc, MembresState>(
listener: (context, state) {
// Après création : recharger la liste
// Après création : afficher le mot de passe temporaire si disponible, puis recharger
if (state is MembreCreated) {
context.read<MembresBloc>().add(const LoadMembres(refresh: true));
final motDePasse = state.membre.motDePasseTemporaire;
if (motDePasse != null && motDePasse.isNotEmpty) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
title: const Text('Compte créé avec succès'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Le membre ${state.membre.nomComplet} a été créé.'),
const SizedBox(height: 12),
const Text(
'Mot de passe temporaire :',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
SelectableText(
motDePasse,
style: const TextStyle(
fontSize: 18,
fontFamily: 'monospace',
letterSpacing: 2,
),
),
const SizedBox(height: 12),
const Text(
'Communiquez ce mot de passe au membre. Il devra le changer à sa première connexion.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(_).pop(),
child: const Text('OK'),
),
],
),
);
}
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
}
// Gestion des erreurs avec SnackBar
@@ -67,7 +113,7 @@ class MembersPageConnected extends StatelessWidget {
label: 'Réessayer',
textColor: Colors.white,
onPressed: () {
context.read<MembresBloc>().add(const LoadMembres());
context.read<MembresBloc>().add(LoadMembres(organisationId: organisationId));
},
),
),
@@ -134,19 +180,23 @@ class MembersPageConnected extends StatelessWidget {
totalPages: state.totalPages,
onPageChanged: (newPage, recherche) {
AppLogger.userAction('Load page', data: {'page': newPage});
context.read<MembresBloc>().add(LoadMembres(page: newPage, recherche: recherche));
context.read<MembresBloc>().add(LoadMembres(page: newPage, recherche: recherche, organisationId: organisationId));
},
onRefresh: () {
AppLogger.userAction('Refresh membres');
context.read<MembresBloc>().add(const LoadMembres(refresh: true));
context.read<MembresBloc>().add(LoadMembres(refresh: true, organisationId: organisationId));
},
onSearch: (query) {
context.read<MembresBloc>().add(LoadMembres(page: 0, recherche: query));
context.read<MembresBloc>().add(LoadMembres(page: 0, recherche: query, organisationId: organisationId));
},
onAddMember: () async {
final bloc = context.read<MembresBloc>();
await showDialog<void>(
context: context,
builder: (_) => const AddMemberDialog(),
builder: (_) => BlocProvider.value(
value: bloc,
child: const AddMemberDialog(),
),
);
},
);
@@ -160,7 +210,7 @@ class MembersPageConnected extends StatelessWidget {
child: NetworkErrorWidget(
onRetry: () {
AppLogger.userAction('Retry load membres after network error');
context.read<MembresBloc>().add(const LoadMembres());
context.read<MembresBloc>().add(LoadMembres(organisationId: organisationId));
},
),
);
@@ -175,7 +225,7 @@ class MembersPageConnected extends StatelessWidget {
message: state.message,
onRetry: () {
AppLogger.userAction('Retry load membres after error');
context.read<MembresBloc>().add(const LoadMembres());
context.read<MembresBloc>().add(LoadMembres(organisationId: organisationId));
},
),
);