Refactoring - Version stable

This commit is contained in:
dahoud
2026-03-28 14:35:32 +00:00
parent d0e61ead97
commit 95d0e4502e
5 changed files with 141 additions and 17 deletions

View File

@@ -64,6 +64,9 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load member dashboard: ${response.statusCode}'); throw ServerException('Failed to load member dashboard: ${response.statusCode}');
} }
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw const NotFoundException('Profil membre non trouvé');
}
AppLogger.error('DashboardRemoteDataSource: getMemberDashboardData', error: e); AppLogger.error('DashboardRemoteDataSource: getMemberDashboardData', error: e);
throw ServerException('Network error: ${e.message}'); throw ServerException('Network error: ${e.message}');
} catch (e, st) { } catch (e, st) {
@@ -102,6 +105,9 @@ class DashboardRemoteDataSourceImpl implements DashboardRemoteDataSource {
throw ServerException('Failed to load adherent account: ${response.statusCode}'); throw ServerException('Failed to load adherent account: ${response.statusCode}');
} }
} on DioException catch (e) { } on DioException catch (e) {
if (e.response?.statusCode == 404) {
throw const NotFoundException('Compte adhérent non trouvé');
}
AppLogger.error('DashboardRemoteDataSource: getCompteAdherent', error: e); AppLogger.error('DashboardRemoteDataSource: getCompteAdherent', error: e);
throw ServerException('Network error: ${e.message}'); throw ServerException('Network error: ${e.message}');
} catch (e, st) { } catch (e, st) {

View File

@@ -9,6 +9,7 @@ import '../models/membre_dashboard_synthese_model.dart';
import '../models/compte_adherent_model.dart'; import '../models/compte_adherent_model.dart';
import '../../../../core/error/exceptions.dart'; import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart'; import '../../../../core/error/failures.dart';
import '../../../../core/utils/logger.dart';
import '../../../../core/network/network_info.dart'; import '../../../../core/network/network_info.dart';
@LazySingleton(as: DashboardRepository) @LazySingleton(as: DashboardRepository)
@@ -29,6 +30,13 @@ class DashboardRepositoryImpl implements DashboardRepository {
try { try {
final model = await remoteDataSource.getCompteAdherent(); final model = await remoteDataSource.getCompteAdherent();
return Right(_mapCompteToEntity(model)); return Right(_mapCompteToEntity(model));
} on NotFoundException {
return const Left(NotFoundFailure(
'Compte adhérent non trouvé',
null,
false,
'Votre profil membre est en cours de création.',
));
} on ServerException catch (e) { } on ServerException catch (e) {
return Left(ServerFailure(e.message)); return Left(ServerFailure(e.message));
} catch (e) { } catch (e) {
@@ -49,13 +57,26 @@ class DashboardRepositoryImpl implements DashboardRepository {
final useMemberDashboard = organizationId.trim().isEmpty; final useMemberDashboard = organizationId.trim().isEmpty;
if (useMemberDashboard) { if (useMemberDashboard) {
// Chargement parallèle de la synthèse et du compte adhérent unifié // Chargement parallèle de la synthèse et du compte adhérent unifié
final results = await Future.wait([ MembreDashboardSyntheseModel? synthese;
remoteDataSource.getMemberDashboardData(), CompteAdherentModel? compteModel;
remoteDataSource.getCompteAdherent(),
]);
final synthese = results[0] as MembreDashboardSyntheseModel; try {
final compteModel = results[1] as CompteAdherentModel; final results = await Future.wait([
remoteDataSource.getMemberDashboardData(),
remoteDataSource.getCompteAdherent(),
]);
synthese = results[0] as MembreDashboardSyntheseModel;
compteModel = results[1] as CompteAdherentModel;
} on NotFoundException {
// Le membre existe dans Keycloak mais pas encore en base → état intermédiaire
AppLogger.info('DashboardRepository: membre non encore enregistré en base');
return const Left(NotFoundFailure(
'Profil membre non trouvé',
null,
false,
'Votre profil membre est en cours de création.',
));
}
// Fallback : si les montants sont à zéro mais qu'il y a des cotisations, // Fallback : si les montants sont à zéro mais qu'il y a des cotisations,
// on complète avec /api/cotisations/mes-cotisations/synthese // on complète avec /api/cotisations/mes-cotisations/synthese
@@ -76,6 +97,13 @@ class DashboardRepositoryImpl implements DashboardRepository {
final dashboardData = await remoteDataSource.getDashboardData(organizationId, userId); final dashboardData = await remoteDataSource.getDashboardData(organizationId, userId);
return Right(_mapToEntity(dashboardData)); return Right(_mapToEntity(dashboardData));
} on NotFoundException {
return const Left(NotFoundFailure(
'Profil membre non trouvé',
null,
false,
'Votre profil membre est en cours de création.',
));
} on ServerException catch (e) { } on ServerException catch (e) {
return Left(ServerFailure(e.message)); return Left(ServerFailure(e.message));
} catch (e) { } catch (e) {

View File

@@ -94,7 +94,13 @@ class DashboardBloc extends Bloc<DashboardEvent, DashboardState> {
); );
result.fold( result.fold(
(failure) => emit(DashboardError(_mapFailureToMessage(failure))), (failure) {
if (failure is NotFoundFailure) {
emit(const DashboardMemberNotRegistered());
} else {
emit(DashboardError(_mapFailureToMessage(failure)));
}
},
(dashboardData) => emit(DashboardLoaded(dashboardData)), (dashboardData) => emit(DashboardLoaded(dashboardData)),
); );
} }
@@ -271,14 +277,16 @@ class DashboardBloc extends Bloc<DashboardEvent, DashboardState> {
} }
String _mapFailureToMessage(Failure failure) { String _mapFailureToMessage(Failure failure) {
switch (failure.runtimeType) { if (failure is NetworkFailure) {
case ServerFailure: return 'Pas de connexion internet. Vérifiez votre connexion.';
return 'Erreur serveur. Veuillez réessayer.';
case NetworkFailure:
return 'Pas de connexion internet. Vérifiez votre connexion.';
default:
return 'Une erreur inattendue s\'est produite.';
} }
if (failure is UnauthorizedFailure) {
return 'Session expirée. Veuillez vous reconnecter.';
}
if (failure is NotFoundFailure) {
return failure.userFriendlyMessage ?? 'Ressource non trouvée.';
}
return 'Erreur serveur. Veuillez réessayer.';
} }
@override @override

View File

@@ -37,3 +37,9 @@ class DashboardError extends DashboardState {
@override @override
List<Object> get props => [message]; List<Object> get props => [message];
} }
/// État émis quand l'utilisateur est authentifié dans Keycloak
/// mais n'a pas encore de fiche membre en base de données.
class DashboardMemberNotRegistered extends DashboardState {
const DashboardMemberNotRegistered();
}

View File

@@ -60,6 +60,10 @@ class _ConnectedDashboardPageState extends State<ConnectedDashboardPage> with Si
); );
} }
if (state is DashboardMemberNotRegistered) {
return _buildMemberNotRegisteredState();
}
if (state is DashboardError) { if (state is DashboardError) {
return _buildErrorState(state.message); return _buildErrorState(state.message);
} }
@@ -624,6 +628,78 @@ class _ConnectedDashboardPageState extends State<ConnectedDashboardPage> with Si
); );
} }
Widget _buildMemberNotRegisteredState() {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
shape: BoxShape.circle,
),
child: const Icon(
Icons.person_add_alt_1_outlined,
size: 56,
color: Colors.white,
),
),
const SizedBox(height: 28),
const Text(
'Bienvenue dans UnionFlow',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: UnionFlowColors.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
const Text(
'Votre compte est en cours de configuration par un administrateur. '
'Votre tableau de bord sera disponible dès que votre profil membre aura été activé.',
style: TextStyle(
fontSize: 14,
color: UnionFlowColors.textSecondary,
height: 1.5,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
color: UnionFlowColors.unionGreen.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.unionGreen.withOpacity(0.3)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.info_outline, size: 18, color: UnionFlowColors.unionGreen),
SizedBox(width: 10),
Flexible(
child: Text(
'Contactez votre administrateur si ce message persiste.',
style: TextStyle(
fontSize: 13,
color: UnionFlowColors.unionGreen,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
],
),
),
);
}
Widget _buildErrorState(String message) { Widget _buildErrorState(String message) {
return Center( return Center(
child: AnimatedFadeIn( child: AnimatedFadeIn(