import 'package:flutter/material.dart'; import '../services/chat_service.dart'; import '../services/chat_config_loader.dart'; /// Widget pour sélectionner les destinataires avec autocomplete /// Respecte les règles de permissions définies dans chat_config.yaml class RecipientSelector extends StatefulWidget { final Function(List>) onRecipientsSelected; final bool allowMultiple; const RecipientSelector({ super.key, required this.onRecipientsSelected, this.allowMultiple = false, }); @override State createState() => _RecipientSelectorState(); } class _RecipientSelectorState extends State { final _service = ChatService.instance; final _searchController = TextEditingController(); final _selectedRecipients = >[]; List> _suggestions = []; bool _isLoading = false; @override void initState() { super.initState(); _loadInitialRecipients(); } Future _loadInitialRecipients() async { setState(() => _isLoading = true); try { final recipients = await _service.getPossibleRecipients(); setState(() { _suggestions = recipients; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); } } Future _searchRecipients(String query) async { if (query.length < 2) { _loadInitialRecipients(); return; } setState(() => _isLoading = true); try { final recipients = await _service.getPossibleRecipients(search: query); setState(() { _suggestions = recipients; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); } } void _toggleRecipient(Map recipient) { setState(() { if (widget.allowMultiple) { final exists = _selectedRecipients.any((r) => r['id'] == recipient['id']); if (exists) { _selectedRecipients.removeWhere((r) => r['id'] == recipient['id']); } else { _selectedRecipients.add(recipient); } } else { _selectedRecipients.clear(); _selectedRecipients.add(recipient); } }); widget.onRecipientsSelected(_selectedRecipients); } Widget _buildRoleBadge(int role) { final color = ChatConfigLoader.instance.getRoleColor(role); final name = ChatConfigLoader.instance.getRoleName(role); return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _hexToColor(color).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: _hexToColor(color).withOpacity(0.3)), ), child: Text( name, style: TextStyle( fontSize: 11, color: _hexToColor(color), fontWeight: FontWeight.w600, ), ), ); } Color _hexToColor(String hex) { final buffer = StringBuffer(); if (hex.length == 6 || hex.length == 7) buffer.write('ff'); buffer.write(hex.replaceFirst('#', '')); return Color(int.parse(buffer.toString(), radix: 16)); } @override Widget build(BuildContext context) { final currentRole = _service.currentUserRole; final config = ChatConfigLoader.instance.getPossibleRecipientsConfig(currentRole); final canBroadcast = config.any((c) => c['allow_broadcast'] == true); final canSelect = config.any((c) => c['allow_selection'] == true); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // En-tête avec options pour membre role 1 if (currentRole == 1) ...[ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sélectionner les destinataires', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ // Bouton Admin pour contacter tous les admins de l'amicale ActionChip( label: const Text('Administrateurs'), avatar: const Icon(Icons.admin_panel_settings, size: 18), backgroundColor: Colors.red.shade50, onPressed: () async { final allRecipients = await _service.getPossibleRecipients(); setState(() { _selectedRecipients.clear(); // Sélectionner tous les admins de l'amicale (role 2) _selectedRecipients.addAll( allRecipients.where((r) => r['role'] == 2) ); }); widget.onRecipientsSelected(_selectedRecipients); }, ), if (_selectedRecipients.isNotEmpty) ActionChip( label: Text('${_selectedRecipients.length} sélectionné(s)'), avatar: const Icon(Icons.check_circle, size: 18), backgroundColor: Colors.orange.shade50, onPressed: () { setState(() => _selectedRecipients.clear()); widget.onRecipientsSelected(_selectedRecipients); }, ), ], ), const SizedBox(height: 8), Text( 'Ou sélectionnez des membres individuellement :', style: TextStyle( fontSize: 13, color: Colors.grey[600], ), ), ], ), ), const Divider(height: 1), ], // En-tête avec options pour admin role 2 if (currentRole == 2) ...[ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sélectionner les destinataires', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ // Bouton GEOSECTOR pour contacter tous les super-admins ActionChip( label: const Text('GEOSECTOR'), avatar: const Icon(Icons.business, size: 18), backgroundColor: Colors.blue.shade50, onPressed: () async { final allRecipients = await _service.getPossibleRecipients(); setState(() { _selectedRecipients.clear(); // Sélectionner tous les super-admins (role 9) _selectedRecipients.addAll( allRecipients.where((r) => r['role'] == 9) ); }); widget.onRecipientsSelected(_selectedRecipients); }, ), // Bouton Amicale pour contacter tous les membres de son amicale ActionChip( label: const Text('Toute l\'Amicale'), avatar: const Icon(Icons.group, size: 18), backgroundColor: Colors.green.shade50, onPressed: () async { final allRecipients = await _service.getPossibleRecipients(); setState(() { _selectedRecipients.clear(); // Sélectionner tous les membres de l'amicale (role 1) _selectedRecipients.addAll( allRecipients.where((r) => r['role'] == 1) ); }); widget.onRecipientsSelected(_selectedRecipients); }, ), if (_selectedRecipients.isNotEmpty) ActionChip( label: Text('${_selectedRecipients.length} sélectionné(s)'), avatar: const Icon(Icons.check_circle, size: 18), backgroundColor: Colors.orange.shade50, onPressed: () { setState(() => _selectedRecipients.clear()); widget.onRecipientsSelected(_selectedRecipients); }, ), ], ), const SizedBox(height: 8), Text( 'Ou sélectionnez des membres individuellement :', style: TextStyle( fontSize: 13, color: Colors.grey[600], ), ), ], ), ), const Divider(height: 1), ], // En-tête avec options pour super-admin if (currentRole == 9) ...[ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sélectionner les destinataires', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Wrap( spacing: 8, children: [ if (canBroadcast) ActionChip( label: const Text('Tous les admins'), avatar: const Icon(Icons.groups, size: 18), onPressed: () async { final allAdmins = await _service.getPossibleRecipients(); setState(() { _selectedRecipients.clear(); _selectedRecipients.addAll( allAdmins.where((r) => r['role'] == 2) ); }); widget.onRecipientsSelected(_selectedRecipients); }, ), if (_selectedRecipients.isNotEmpty) ActionChip( label: Text('${_selectedRecipients.length} sélectionné(s)'), avatar: const Icon(Icons.check_circle, size: 18), backgroundColor: Colors.green.shade50, onPressed: () { setState(() => _selectedRecipients.clear()); widget.onRecipientsSelected(_selectedRecipients); }, ), ], ), ], ), ), const Divider(height: 1), ], // Barre de recherche Padding( padding: const EdgeInsets.all(16), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: ChatConfigLoader.instance.getUIMessages()['search_placeholder'] ?? 'Rechercher...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), onChanged: _searchRecipients, ), ), // Liste des suggestions Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _suggestions.isEmpty ? Center( child: Text( ChatConfigLoader.instance.getUIMessages()['no_recipients'] ?? 'Aucun destinataire disponible', style: TextStyle(color: Colors.grey[600]), ), ) : ListView.builder( itemCount: _suggestions.length, itemBuilder: (context, index) { final recipient = _suggestions[index]; final isSelected = _selectedRecipients.any( (r) => r['id'] == recipient['id'] ); return ListTile( leading: CircleAvatar( backgroundColor: isSelected ? Theme.of(context).primaryColor : Colors.grey.shade200, child: Text( recipient['name']?.substring(0, 1).toUpperCase() ?? '?', style: TextStyle( color: isSelected ? Colors.white : Colors.grey[700], fontWeight: FontWeight.w600, ), ), ), title: Text( recipient['name'] ?? 'Sans nom', style: const TextStyle(fontWeight: FontWeight.w500), ), subtitle: recipient['entite_name'] != null ? Text( recipient['entite_name'], style: TextStyle( fontSize: 13, color: Colors.grey[600], ), ) : null, trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (recipient['role'] != null) _buildRoleBadge(recipient['role']), if (widget.allowMultiple || canSelect) Checkbox( value: isSelected, onChanged: (_) => _toggleRecipient(recipient), ), ], ), onTap: () => _toggleRecipient(recipient), ); }, ), ), // Bouton de validation if (_selectedRecipients.isNotEmpty) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, border: Border( top: BorderSide(color: Colors.grey.shade200), ), ), child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.of(context).pop(_selectedRecipients), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), backgroundColor: Theme.of(context).primaryColor, ), child: Text( widget.allowMultiple ? 'Créer conversation avec ${_selectedRecipients.length} personne(s)' : 'Créer conversation', style: const TextStyle( fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ), ], ); } @override void dispose() { _searchController.dispose(); super.dispose(); } } /// Dialog pour sélectionner les destinataires class RecipientSelectorDialog extends StatelessWidget { final bool allowMultiple; const RecipientSelectorDialog({ super.key, this.allowMultiple = false, }); static Future?> show( BuildContext context, { bool allowMultiple = false, }) async { return showDialog>( context: context, builder: (context) => RecipientSelectorDialog( allowMultiple: allowMultiple, ), ); } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.8, maxWidth: 500, ), child: _RecipientSelectorWithMessage( allowMultiple: allowMultiple, ), ), ); } } /// Widget interne pour gérer la sélection et le message initial class _RecipientSelectorWithMessage extends StatefulWidget { final bool allowMultiple; const _RecipientSelectorWithMessage({ required this.allowMultiple, }); @override State<_RecipientSelectorWithMessage> createState() => _RecipientSelectorWithMessageState(); } class _RecipientSelectorWithMessageState extends State<_RecipientSelectorWithMessage> { List> _selectedRecipients = []; final _messageController = TextEditingController(); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ // Sélecteur de destinataires Expanded( child: RecipientSelector( allowMultiple: widget.allowMultiple, onRecipientsSelected: (recipients) { setState(() { _selectedRecipients = recipients; }); }, ), ), // Champ de message initial si des destinataires sont sélectionnés if (_selectedRecipients.isNotEmpty) ...[ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade50, border: Border( top: BorderSide(color: Colors.grey.shade200), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Message initial (optionnel)', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1E293B), ), ), const SizedBox(height: 8), TextField( controller: _messageController, decoration: InputDecoration( hintText: 'Écrivez votre premier message...', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), ), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), ), maxLines: 3, minLines: 2, ), ], ), ), // Bouton de validation Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, border: Border( top: BorderSide(color: Colors.grey.shade200), ), ), child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { Navigator.of(context).pop({ 'recipients': _selectedRecipients, 'initial_message': _messageController.text.trim(), }); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), backgroundColor: Theme.of(context).primaryColor, ), child: Text( widget.allowMultiple ? 'Créer conversation avec ${_selectedRecipients.length} personne(s)' : 'Créer conversation', style: const TextStyle( fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ), ], ], ); } @override void dispose() { _messageController.dispose(); super.dispose(); } }