import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import '../../../../core/utils/logger.dart'; import '../../bloc/admin_users_bloc.dart'; import '../../data/models/admin_user_model.dart'; import '../../data/repositories/admin_user_repository.dart'; import '../../../organizations/data/models/organization_model.dart'; import '../../../organizations/data/services/organization_service.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/widgets/core_card.dart'; import '../../../../shared/design_system/components/uf_app_bar.dart'; import '../../../../shared/design_system/components/uf_buttons.dart'; /// Page détail d'un utilisateur + édition des rôles class UserManagementDetailPage extends StatelessWidget { final String userId; const UserManagementDetailPage({super.key, required this.userId}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: UFAppBar( title: 'Détail utilisateur', moduleGradient: ModuleColors.systemeGradient, ), body: SafeArea( top: false, child: BlocBuilder( builder: (context, state) { if (state is AdminUsersLoading) { return const Center(child: CircularProgressIndicator()); } if (state is AdminUsersError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: AppColors.error), const SizedBox(height: 12), Text(state.message, textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => context.read() .add(AdminUserDetailWithRolesRequested(userId)), icon: const Icon(Icons.refresh, size: 16), label: const Text('Réessayer'), ), ], ), ); } if (state is AdminUserDetailLoaded) { return _UserDetailContent( user: state.user, userRoles: state.userRoles, allRoles: state.allRoles, userId: userId, ); } // AdminUserRolesUpdated : rechargement en cours — garder un indicateur léger if (state is AdminUserRolesUpdated) { return const Center(child: CircularProgressIndicator()); } return const SizedBox(); }, ), ), ); } } class _UserDetailContent extends StatefulWidget { final AdminUserModel user; final List userRoles; final List allRoles; final String userId; const _UserDetailContent({ required this.user, required this.userRoles, required this.allRoles, required this.userId, }); @override State<_UserDetailContent> createState() => _UserDetailContentState(); } class _UserDetailContentState extends State<_UserDetailContent> { late Set _selectedRoleNames; @override void initState() { super.initState(); _selectedRoleNames = widget.userRoles.map((r) => r.name).toSet(); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { if (state is AdminUserRolesUpdated) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Rôles mis à jour')), ); } }, child: RefreshIndicator( color: ModuleColors.systeme, onRefresh: () async => context .read() .add(AdminUserDetailWithRolesRequested(widget.userId)), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CoreCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.user.displayName, style: AppTypography.headerSmall), const SizedBox(height: 8), if (widget.user.email != null) Text('Email: ${widget.user.email}', style: AppTypography.bodyTextSmall), if (widget.user.username != null) Text('Username: ${widget.user.username}', style: AppTypography.bodyTextSmall), Text( 'Statut: ${widget.user.enabled == true ? "Actif" : "Inactif"}', style: AppTypography.bodyTextSmall.copyWith( color: widget.user.enabled == true ? AppColors.success : AppColors.error, fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(height: 8), CoreCard( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.manage_accounts_outlined, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16), const SizedBox(width: 8), Text( 'RÔLES', style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.1, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const Spacer(), Text( '${_selectedRoleNames.length} sélectionné(s)', style: AppTypography.subtitleSmall.copyWith( fontSize: 10, color: AppColors.primary, ), ), ], ), const SizedBox(height: 8), if (widget.allRoles.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( children: [ const Icon(Icons.warning_amber_outlined, size: 16, color: AppColors.error), const SizedBox(width: 8), Expanded( child: Text( 'Impossible de charger les rôles disponibles.', style: AppTypography.bodyTextSmall.copyWith( color: AppColors.error), ), ), TextButton.icon( onPressed: () => context.read() .add(AdminUserDetailWithRolesRequested(widget.userId)), icon: const Icon(Icons.refresh, size: 14), label: const Text('Recharger'), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), ], ), ) else ...widget.allRoles.map((role) { final selected = _selectedRoleNames.contains(role.name); return CheckboxListTile( title: Text(role.name, style: AppTypography.bodyTextSmall), subtitle: role.description != null && role.description!.isNotEmpty ? Text(role.description!, style: AppTypography.subtitleSmall.copyWith(fontSize: 10)) : null, activeColor: AppColors.primary, contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, value: selected, onChanged: (v) => setState(() { if (v == true) { _selectedRoleNames.add(role.name); } else { _selectedRoleNames.remove(role.name); } }), ); }), ], ), ), const SizedBox(height: 8), if (widget.allRoles.isNotEmpty) UFPrimaryButton( label: 'Enregistrer les rôles', onPressed: () => context.read().add( AdminUserRolesUpdateRequested( widget.userId, _selectedRoleNames.toList())), ), const SizedBox(height: 12), const Divider(height: 1), const SizedBox(height: 8), Text( 'ASSOCIER À UNE ORGANISATION', style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.1, ), ), const SizedBox(height: 8), Text( 'Permet à cet utilisateur (ex. admin d\'organisation) de voir « Mes organisations » et d\'accéder au dashboard de l\'organisation.', style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: widget.user.email == null || widget.user.email!.isEmpty ? null : () => _openAssocierOrganisationDialog(context, widget.user.email!), icon: const Icon(Icons.business, size: 18), label: const Text('Associer à une organisation'), style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, side: const BorderSide(color: AppColors.primary), ), ), ], ), ), // SingleChildScrollView ), // RefreshIndicator ); } Future _openAssocierOrganisationDialog(BuildContext context, String userEmail) async { final orgService = GetIt.I(); final adminRepo = GetIt.I(); List organisations = []; try { organisations = await orgService.getOrganizations(page: 0, size: 200); } catch (e, st) { AppLogger.error('UserManagementDetail: chargement organisations échoué', error: e, stackTrace: st); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Impossible de charger les organisations')), ); return; } if (!context.mounted) return; final orgsWithId = organisations.where((o) => o.id != null && o.id!.isNotEmpty).toList(); if (orgsWithId.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Aucune organisation disponible. Créez-en une d\'abord.')), ); return; } String? selectedOrgId = orgsWithId.first.id; await showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx2, setDialogState) { return AlertDialog( title: const Text('Associer à une organisation'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Utilisateur: $userEmail', style: AppTypography.bodyTextSmall), const SizedBox(height: 16), DropdownButtonFormField( value: selectedOrgId, isExpanded: true, decoration: const InputDecoration( labelText: 'Organisation', border: OutlineInputBorder(), ), items: orgsWithId .map((o) => DropdownMenuItem(value: o.id, child: Text(o.nom, overflow: TextOverflow.ellipsis))) .toList(), onChanged: (v) => setDialogState(() => selectedOrgId = v), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('Annuler'), ), FilledButton( onPressed: () async { if (selectedOrgId == null) return; try { await adminRepo.associerOrganisation(email: userEmail, organisationId: selectedOrgId!); if (ctx.mounted) Navigator.of(ctx).pop(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Utilisateur associé à l\'organisation avec succès.')), ); } } catch (e) { if (ctx.mounted) { ScaffoldMessenger.of(ctx).showSnackBar( SnackBar(content: Text('Erreur: ${e.toString().replaceFirst('Exception: ', '')}')), ); } } }, child: const Text('Associer'), ), ], ); }, ), ); } }