/// BLoC pour le sélecteur d'organisation multi-org. /// /// Charge GET /api/membres/mes-organisations et maintient /// l'organisation active via [OrgContextService]. library org_switcher_bloc; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:injectable/injectable.dart'; import 'package:dio/dio.dart'; import '../../../core/network/api_client.dart'; import '../../../core/network/org_context_service.dart'; import '../../../core/utils/logger.dart'; import '../data/models/org_switcher_entry.dart'; // ─── ÉVÉNEMENTS ───────────────────────────────────────────────────────────── abstract class OrgSwitcherEvent extends Equatable { const OrgSwitcherEvent(); @override List get props => []; } /// Charge la liste des organisations du membre connecté. class OrgSwitcherLoadRequested extends OrgSwitcherEvent { const OrgSwitcherLoadRequested(); } /// Sélectionne une organisation comme active. class OrgSwitcherSelectRequested extends OrgSwitcherEvent { final OrgSwitcherEntry organisation; const OrgSwitcherSelectRequested(this.organisation); @override List get props => [organisation]; } // ─── ÉTATS ────────────────────────────────────────────────────────────────── abstract class OrgSwitcherState extends Equatable { const OrgSwitcherState(); @override List get props => []; } class OrgSwitcherInitial extends OrgSwitcherState {} class OrgSwitcherLoading extends OrgSwitcherState {} class OrgSwitcherLoaded extends OrgSwitcherState { final List organisations; final OrgSwitcherEntry? active; const OrgSwitcherLoaded({required this.organisations, this.active}); OrgSwitcherLoaded copyWith({ List? organisations, OrgSwitcherEntry? active, }) { return OrgSwitcherLoaded( organisations: organisations ?? this.organisations, active: active ?? this.active, ); } @override List get props => [organisations, active]; } class OrgSwitcherError extends OrgSwitcherState { final String message; const OrgSwitcherError(this.message); @override List get props => [message]; } // ─── BLOC ──────────────────────────────────────────────────────────────────── @injectable class OrgSwitcherBloc extends Bloc { final ApiClient _apiClient; final OrgContextService _orgContextService; static const String _endpoint = '/api/membres/mes-organisations'; OrgSwitcherBloc(this._apiClient, this._orgContextService) : super(OrgSwitcherInitial()) { on(_onLoad); on(_onSelect); } Future _onLoad( OrgSwitcherLoadRequested event, Emitter emit, ) async { emit(OrgSwitcherLoading()); try { final response = await _apiClient.get>(_endpoint); final rawList = response.data as List? ?? []; final orgs = rawList .map((e) => OrgSwitcherEntry.fromJson(e as Map)) .toList(); // Auto-select si une seule organisation ou si une org est déjà active OrgSwitcherEntry? active; if (_orgContextService.hasContext) { active = orgs.where((o) => o.organisationId == _orgContextService.activeOrganisationId).firstOrNull; } active ??= orgs.isNotEmpty ? orgs.first : null; if (active != null && !_orgContextService.hasContext) { _applyActiveOrg(active); } emit(OrgSwitcherLoaded(organisations: orgs, active: active)); } on DioException catch (e) { AppLogger.warning('OrgSwitcherBloc: erreur réseau: ${e.message}'); emit(OrgSwitcherError('Impossible de charger vos organisations: ${e.message}')); } catch (e) { AppLogger.error('OrgSwitcherBloc: erreur inattendue: $e'); emit(OrgSwitcherError('Erreur inattendue: $e')); } } Future _onSelect( OrgSwitcherSelectRequested event, Emitter emit, ) async { final current = state; if (current is! OrgSwitcherLoaded) return; _applyActiveOrg(event.organisation); emit(current.copyWith(active: event.organisation)); AppLogger.info('OrgSwitcherBloc: sélection → ${event.organisation.nom}'); } void _applyActiveOrg(OrgSwitcherEntry org) { _orgContextService.setActiveOrganisation( organisationId: org.organisationId, nom: org.nom, type: org.typeOrganisation, modulesActifsCsv: org.modulesActifs, ); } }