feat: Release version 3.1.4 - Mode terrain et génération PDF
✨ Nouvelles fonctionnalités: - Ajout du mode terrain pour utilisation mobile hors connexion - Génération automatique de reçus PDF avec template personnalisé - Révision complète du système de cartes avec amélioration des performances 🔧 Améliorations techniques: - Refactoring du module chat avec architecture simplifiée - Optimisation du système de sécurité NIST SP 800-63B - Amélioration de la gestion des secteurs géographiques - Support UTF-8 étendu pour les noms d'utilisateurs 📱 Application mobile: - Nouveau mode terrain dans user_field_mode_page - Interface utilisateur adaptative pour conditions difficiles - Synchronisation offline améliorée 🗺️ Cartographie: - Optimisation des performances MapBox - Meilleure gestion des tuiles hors ligne - Amélioration de l'affichage des secteurs 📄 Documentation: - Ajout guide Android (ANDROID-GUIDE.md) - Documentation sécurité API (API-SECURITY.md) - Guide module chat (CHAT_MODULE.md) 🐛 Corrections: - Résolution des erreurs 400 lors de la création d'utilisateurs - Correction de la validation des noms d'utilisateurs - Fix des problèmes de synchronisation chat 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import 'package:geosector_app/presentation/widgets/chat/chat_sidebar.dart';
|
||||
import 'package:geosector_app/presentation/widgets/chat/chat_messages.dart';
|
||||
import 'package:geosector_app/presentation/widgets/chat/chat_input.dart';
|
||||
import 'package:geosector_app/chat/chat_module.dart';
|
||||
import 'package:geosector_app/chat/pages/rooms_page_embedded.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/current_amicale_service.dart';
|
||||
|
||||
class AdminCommunicationPage extends StatefulWidget {
|
||||
const AdminCommunicationPage({super.key});
|
||||
@@ -12,546 +14,238 @@ class AdminCommunicationPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AdminCommunicationPageState extends State<AdminCommunicationPage> {
|
||||
int selectedContactId = 0;
|
||||
String selectedContactName = '';
|
||||
bool isTeamChat = true;
|
||||
String messageText = '';
|
||||
bool isReplying = false;
|
||||
Map<String, dynamic>? replyingTo;
|
||||
bool _isChatInitialized = false;
|
||||
bool _isInitializing = false;
|
||||
String? _initError;
|
||||
GlobalKey<RoomsPageEmbeddedState>? _roomsPageKey;
|
||||
|
||||
// Données simulées pour les conversations d'équipe
|
||||
final List<Map<String, dynamic>> teamContacts = [
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'Équipe',
|
||||
'isGroup': true,
|
||||
'lastMessage': 'Réunion à 14h aujourd\'hui',
|
||||
'time': DateTime.now().subtract(const Duration(minutes: 30)),
|
||||
'unread': 2,
|
||||
'online': true,
|
||||
'avatar': 'assets/images/team.png',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': 'Jean Dupont',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'Je serai présent demain',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 1)),
|
||||
'unread': 0,
|
||||
'online': true,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'name': 'Marie Martin',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'Secteur Sud terminé',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 3)),
|
||||
'unread': 1,
|
||||
'online': false,
|
||||
'avatar': 'assets/images/avatar2.png',
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'name': 'Pierre Legrand',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'J\'ai une question sur mon secteur',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1)),
|
||||
'unread': 0,
|
||||
'online': false,
|
||||
'avatar': 'assets/images/avatar3.png',
|
||||
},
|
||||
];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeChat();
|
||||
}
|
||||
|
||||
// Données simulées pour les conversations clients
|
||||
final List<Map<String, dynamic>> clientContacts = [
|
||||
{
|
||||
'id': 101,
|
||||
'name': 'Martin Durand',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'Merci pour votre passage',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 5)),
|
||||
'unread': 0,
|
||||
'online': false,
|
||||
'avatar': null,
|
||||
'email': 'martin.durand@example.com',
|
||||
},
|
||||
{
|
||||
'id': 102,
|
||||
'name': 'Sophie Lambert',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'Question concernant le reçu',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1)),
|
||||
'unread': 3,
|
||||
'online': false,
|
||||
'avatar': null,
|
||||
'email': 'sophie.lambert@example.com',
|
||||
},
|
||||
{
|
||||
'id': 103,
|
||||
'name': 'Thomas Bernard',
|
||||
'isGroup': false,
|
||||
'lastMessage': 'Rendez-vous manqué',
|
||||
'time': DateTime.now().subtract(const Duration(days: 2)),
|
||||
'unread': 0,
|
||||
'online': false,
|
||||
'avatar': null,
|
||||
'email': 'thomas.bernard@example.com',
|
||||
},
|
||||
];
|
||||
Future<void> _initializeChat() async {
|
||||
if (_isInitializing) return;
|
||||
|
||||
setState(() {
|
||||
_isInitializing = true;
|
||||
_initError = null;
|
||||
});
|
||||
|
||||
// Messages simulés pour la conversation sélectionnée
|
||||
final Map<int, List<Map<String, dynamic>>> chatMessages = {
|
||||
1: [
|
||||
{
|
||||
'id': 1,
|
||||
'senderId': 2,
|
||||
'senderName': 'Jean Dupont',
|
||||
'message':
|
||||
'Bonjour à tous, comment avance la collecte dans vos secteurs ?',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 3)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'senderId': 3,
|
||||
'senderName': 'Marie Martin',
|
||||
'message': 'J\'ai terminé le secteur Sud avec 45 passages réalisés !',
|
||||
'time': DateTime.now()
|
||||
.subtract(const Duration(days: 1, hours: 2, minutes: 30)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar2.png',
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'senderId': 4,
|
||||
'senderName': 'Pierre Legrand',
|
||||
'message':
|
||||
'Secteur Est en cours, j\'ai réalisé 28 passages pour l\'instant.',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 2)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar3.png',
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message':
|
||||
'Parfait, n\'oubliez pas la réunion de demain à 14h pour faire le point !',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 1)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'senderId': 2,
|
||||
'senderName': 'Jean Dupont',
|
||||
'message': 'Je serai présent 👍',
|
||||
'time': DateTime.now().subtract(const Duration(minutes: 30)),
|
||||
'isRead': false,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
],
|
||||
2: [
|
||||
{
|
||||
'id': 101,
|
||||
'senderId': 2,
|
||||
'senderName': 'Jean Dupont',
|
||||
'message':
|
||||
'Bonjour, est-ce que je peux commencer le secteur Ouest demain ?',
|
||||
'time': DateTime.now().subtract(const Duration(days: 2)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
{
|
||||
'id': 102,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message': 'Bonjour Jean, oui bien sûr. Les documents sont prêts.',
|
||||
'time': DateTime.now()
|
||||
.subtract(const Duration(days: 2))
|
||||
.add(const Duration(minutes: 15)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 103,
|
||||
'senderId': 2,
|
||||
'senderName': 'Jean Dupont',
|
||||
'message': 'Merci ! Je passerai les récupérer ce soir.',
|
||||
'time': DateTime.now()
|
||||
.subtract(const Duration(days: 2))
|
||||
.add(const Duration(minutes: 20)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
{
|
||||
'id': 104,
|
||||
'senderId': 2,
|
||||
'senderName': 'Jean Dupont',
|
||||
'message': 'Je serai présent à la réunion de demain.',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 1)),
|
||||
'isRead': true,
|
||||
'avatar': 'assets/images/avatar1.png',
|
||||
},
|
||||
],
|
||||
101: [
|
||||
{
|
||||
'id': 201,
|
||||
'senderId': 101,
|
||||
'senderName': 'Martin Durand',
|
||||
'message':
|
||||
'Bonjour, je voulais vous remercier pour votre passage. J\'ai bien reçu le reçu par email.',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 5)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 202,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message':
|
||||
'Bonjour M. Durand, je vous remercie pour votre contribution. N\'hésitez pas si vous avez des questions.',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 4)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 203,
|
||||
'senderId': 101,
|
||||
'senderName': 'Martin Durand',
|
||||
'message': 'Tout est parfait, merci !',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 5)),
|
||||
'isRead': true,
|
||||
},
|
||||
],
|
||||
102: [
|
||||
{
|
||||
'id': 301,
|
||||
'senderId': 102,
|
||||
'senderName': 'Sophie Lambert',
|
||||
'message':
|
||||
'Bonjour, je n\'ai pas reçu le reçu suite à mon paiement d\'hier. Pouvez-vous vérifier ?',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 3)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 302,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message':
|
||||
'Bonjour Mme Lambert, je m\'excuse pour ce désagrément. Je vérifie cela immédiatement.',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 2)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 303,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message':
|
||||
'Il semble qu\'il y ait eu un problème technique. Je viens de renvoyer le reçu à votre adresse email. Pourriez-vous vérifier si vous l\'avez bien reçu ?',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1, hours: 1)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 304,
|
||||
'senderId': 102,
|
||||
'senderName': 'Sophie Lambert',
|
||||
'message':
|
||||
'Je n\'ai toujours rien reçu. Mon email est-il correct ? C\'est sophie.lambert@example.com',
|
||||
'time': DateTime.now().subtract(const Duration(days: 1)),
|
||||
'isRead': true,
|
||||
},
|
||||
{
|
||||
'id': 305,
|
||||
'senderId': 102,
|
||||
'senderName': 'Sophie Lambert',
|
||||
'message': 'Est-ce que vous pouvez réessayer ?',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 5)),
|
||||
'isRead': false,
|
||||
},
|
||||
{
|
||||
'id': 306,
|
||||
'senderId': 102,
|
||||
'senderName': 'Sophie Lambert',
|
||||
'message': 'Toujours pas de nouvelles...',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 3)),
|
||||
'isRead': false,
|
||||
},
|
||||
{
|
||||
'id': 307,
|
||||
'senderId': 102,
|
||||
'senderName': 'Sophie Lambert',
|
||||
'message': 'Pouvez-vous me contacter dès que possible ?',
|
||||
'time': DateTime.now().subtract(const Duration(hours: 1)),
|
||||
'isRead': false,
|
||||
},
|
||||
],
|
||||
};
|
||||
try {
|
||||
// Récupérer les informations utilisateur
|
||||
final currentUser = CurrentUserService.instance;
|
||||
final apiService = ApiService.instance;
|
||||
final currentAmicale = CurrentAmicaleService.instance.currentAmicale;
|
||||
|
||||
if (currentUser.currentUser == null) {
|
||||
throw Exception('Administrateur non connecté');
|
||||
}
|
||||
|
||||
// Initialiser le module chat avec les informations de l'administrateur
|
||||
await ChatModule.init(
|
||||
apiUrl: apiService.baseUrl,
|
||||
userId: currentUser.currentUser!.id,
|
||||
userName: currentUser.userName ?? currentUser.userEmail ?? 'Administrateur',
|
||||
userRole: currentUser.currentUser!.role,
|
||||
userEntite: currentUser.fkEntite ?? currentAmicale?.id,
|
||||
authToken: currentUser.sessionId,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_isChatInitialized = true;
|
||||
_isInitializing = false;
|
||||
_roomsPageKey = GlobalKey<RoomsPageEmbeddedState>();
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_initError = e.toString();
|
||||
_isInitializing = false;
|
||||
});
|
||||
debugPrint('Erreur initialisation chat admin: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _refreshRooms() {
|
||||
_roomsPageKey?.currentState?.refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isDesktop = screenWidth > 800;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
// Sidebar des contacts (fixe sur desktop, conditional sur mobile)
|
||||
if (isDesktop || selectedContactId == 0)
|
||||
SizedBox(
|
||||
width: isDesktop ? 320 : screenWidth,
|
||||
child: ChatSidebar(
|
||||
teamContacts: teamContacts,
|
||||
clientContacts: clientContacts,
|
||||
isTeamChat: isTeamChat,
|
||||
selectedContactId: selectedContactId,
|
||||
onContactSelected: (contactId, contactName, isTeam) {
|
||||
setState(() {
|
||||
selectedContactId = contactId;
|
||||
selectedContactName = contactName;
|
||||
isTeamChat = isTeam;
|
||||
replyingTo = null;
|
||||
isReplying = false;
|
||||
});
|
||||
},
|
||||
onToggleGroup: (isTeam) {
|
||||
setState(() {
|
||||
isTeamChat = isTeam;
|
||||
selectedContactId = 0;
|
||||
selectedContactName = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: theme.shadowColor.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 1,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
||||
// Vue des messages (conditionnelle sur mobile)
|
||||
if (isDesktop || selectedContactId != 0)
|
||||
Expanded(
|
||||
child: selectedContactId == 0
|
||||
? const Center(
|
||||
child: Text('Sélectionnez une conversation pour commencer'),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
// En-tête de la conversation
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppTheme.spacingL,
|
||||
vertical: AppTheme.spacingM,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (!isDesktop)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
selectedContactId = 0;
|
||||
selectedContactName = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor:
|
||||
AppTheme.primaryColor.withOpacity(0.2),
|
||||
backgroundImage:
|
||||
_getAvatarForContact(selectedContactId),
|
||||
child: _getAvatarForContact(selectedContactId) ==
|
||||
null
|
||||
? Text(
|
||||
selectedContactName.isNotEmpty
|
||||
? selectedContactName[0].toUpperCase()
|
||||
: '',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: AppTheme.spacingM),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
selectedContactName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
if (!isTeamChat && selectedContactId > 100)
|
||||
Text(
|
||||
_getEmailForContact(selectedContactId),
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info_outline),
|
||||
onPressed: () {
|
||||
// Afficher les détails du contact
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Messages
|
||||
Expanded(
|
||||
child: ChatMessages(
|
||||
messages: chatMessages[selectedContactId] ?? [],
|
||||
currentUserId: 0,
|
||||
onReply: (message) {
|
||||
setState(() {
|
||||
isReplying = true;
|
||||
replyingTo = message;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Zone de réponse
|
||||
if (isReplying)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(AppTheme.spacingM),
|
||||
color: Colors.grey[100],
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppTheme.spacingM),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Réponse à ${replyingTo?['senderName']}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
replyingTo?['message'] ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isReplying = false;
|
||||
replyingTo = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Zone de saisie du message
|
||||
ChatInput(
|
||||
onMessageSent: (text) {
|
||||
setState(() {
|
||||
// Ajouter le message à la conversation
|
||||
if (chatMessages[selectedContactId] != null) {
|
||||
final newMessageId =
|
||||
chatMessages[selectedContactId]!.last['id'] +
|
||||
1;
|
||||
|
||||
chatMessages[selectedContactId]!.add({
|
||||
'id': newMessageId,
|
||||
'senderId': 0,
|
||||
'senderName': 'Vous',
|
||||
'message': text,
|
||||
'time': DateTime.now(),
|
||||
'isRead': false,
|
||||
'replyTo': isReplying ? replyingTo : null,
|
||||
});
|
||||
|
||||
// Mise à jour du dernier message pour le contact
|
||||
final contactsList =
|
||||
isTeamChat ? teamContacts : clientContacts;
|
||||
final contactIndex = contactsList.indexWhere(
|
||||
(c) => c['id'] == selectedContactId);
|
||||
|
||||
if (contactIndex != -1) {
|
||||
contactsList[contactIndex]['lastMessage'] =
|
||||
text;
|
||||
contactsList[contactIndex]['time'] =
|
||||
DateTime.now();
|
||||
contactsList[contactIndex]['unread'] = 0;
|
||||
}
|
||||
|
||||
isReplying = false;
|
||||
replyingTo = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: _buildContent(theme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ImageProvider? _getAvatarForContact(int contactId) {
|
||||
String? avatarPath;
|
||||
|
||||
if (isTeamChat) {
|
||||
final contact = teamContacts.firstWhere(
|
||||
(c) => c['id'] == contactId,
|
||||
orElse: () => {'avatar': null},
|
||||
Widget _buildContent(ThemeData theme) {
|
||||
if (_isInitializing) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Initialisation du chat administrateur...',
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
avatarPath = contact['avatar'];
|
||||
} else {
|
||||
final contact = clientContacts.firstWhere(
|
||||
(c) => c['id'] == contactId,
|
||||
orElse: () => {'avatar': null},
|
||||
);
|
||||
avatarPath = contact['avatar'];
|
||||
}
|
||||
|
||||
return avatarPath != null ? AssetImage(avatarPath) : null;
|
||||
}
|
||||
|
||||
String _getEmailForContact(int contactId) {
|
||||
if (!isTeamChat) {
|
||||
final contact = clientContacts.firstWhere(
|
||||
(c) => c['id'] == contactId,
|
||||
orElse: () => {'email': ''},
|
||||
if (_initError != null) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Erreur d\'initialisation chat',
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_initError!,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _initializeChat,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Réessayer'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.error,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return contact['email'] ?? '';
|
||||
}
|
||||
return '';
|
||||
|
||||
if (_isChatInitialized) {
|
||||
// Afficher le module chat avec un header simplifié
|
||||
return Column(
|
||||
children: [
|
||||
// En-tête simplifié avec boutons intégrés
|
||||
Container(
|
||||
height: 60,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: theme.dividerColor.withOpacity(0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.admin_panel_settings,
|
||||
color: Colors.red.shade600,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Messages Administration',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.red.shade600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// Boutons d'action
|
||||
IconButton(
|
||||
icon: Icon(Icons.add, color: Colors.red.shade600),
|
||||
onPressed: () {
|
||||
// Déclencher la création d'une nouvelle conversation
|
||||
// Cela sera géré par RoomsPageEmbedded
|
||||
_roomsPageKey?.currentState?.createNewConversation();
|
||||
},
|
||||
tooltip: 'Nouvelle conversation',
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.refresh, color: Colors.red.shade600),
|
||||
onPressed: _refreshRooms,
|
||||
tooltip: 'Actualiser',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Module chat sans AppBar
|
||||
Expanded(
|
||||
child: RoomsPageEmbedded(
|
||||
key: _roomsPageKey,
|
||||
onRefreshPressed: () {
|
||||
// Callback optionnel après refresh
|
||||
debugPrint('Conversations actualisées');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// État initial
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.chat_bubble_outline,
|
||||
size: 80,
|
||||
color: theme.colorScheme.primary.withOpacity(0.3),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Chat administrateur non initialisé',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _initializeChat,
|
||||
icon: const Icon(Icons.power_settings_new),
|
||||
label: const Text('Initialiser le chat'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user