From e0083d665b649db8f3454f8127ae1f05ad395821 Mon Sep 17 00:00:00 2001 From: Hanim Date: Fri, 19 Sep 2025 15:44:12 +0200 Subject: [PATCH] Add SidebarAdmin --- .../admin_dashboardScreen.dart | 64 ++- frontend/lib/services/auth_service.dart | 75 +-- .../widgets/admin/DashboardSidebarAdmin.dart | 70 +++ .../admin/Statistique_manage_widget.dart | 15 + .../widgets/admin/admin_manage_widget.dart | 132 +++++ ...sistante_maternelle_management_widget.dart | 8 +- .../widgets/admin/base_user_management.dart | 4 +- .../lib/widgets/admin/dashboard_admin.dart | 109 ++-- .../admin/parent_managmant_widget.dart | 503 +++++------------- .../dashbord_parent/dashboard_app_bar.dart | 61 ++- .../widgets/dashbord_parent/wid_dashbord.dart | 2 - 11 files changed, 561 insertions(+), 482 deletions(-) create mode 100644 frontend/lib/widgets/admin/DashboardSidebarAdmin.dart create mode 100644 frontend/lib/widgets/admin/Statistique_manage_widget.dart create mode 100644 frontend/lib/widgets/admin/admin_manage_widget.dart diff --git a/frontend/lib/screens/administrateurs/admin_dashboardScreen.dart b/frontend/lib/screens/administrateurs/admin_dashboardScreen.dart index d10062e..6c707f1 100644 --- a/frontend/lib/screens/administrateurs/admin_dashboardScreen.dart +++ b/frontend/lib/screens/administrateurs/admin_dashboardScreen.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:p_tits_pas/widgets/admin/DashboardSidebarAdmin.dart'; +import 'package:p_tits_pas/widgets/admin/Statistique_manage_widget.dart'; +import 'package:p_tits_pas/widgets/admin/admin_manage_widget.dart'; import 'package:p_tits_pas/widgets/admin/assistante_maternelle_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/gestionnaire_management_widget.dart'; import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart'; @@ -24,41 +27,58 @@ class _AdminDashboardScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(60.0), - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.grey.shade300), + appBar: PreferredSize( + preferredSize: const Size.fromHeight(60.0), + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey.shade300), + ), + ), + child: DashboardAppBarAdmin( + selectedIndex: selectedIndex, + onTabChange: onTabChange, ), ), - child: DashboardAppBarAdmin( - selectedIndex: selectedIndex, - onTabChange: onTabChange, - ), ), - ), - body: Column( - children: [ - Expanded( - child: _getBody(), - ), - const AppFooter(), - ], - ), - ); + body: Column( + children: [ + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + width: 250, + child: DashboardSidebarAdmin( + selectedIndex: selectedIndex, + onTabChange: onTabChange, + ), + ), + Expanded( + flex: 2, + child: _getBody(), + ), + ], + ), + ), + const AppFooter(), + ], + ), + ); } Widget _getBody() { switch (selectedIndex) { case 0: - return const GestionnaireManagementWidget(); + return const GestionnaireManagementWidget(); case 1: return const ParentManagementWidget(); case 2: return const AssistanteMaternelleManagementWidget(); case 3: - return const Center(child: Text("👨‍💼 Administrateurs")); + return const AdministrateurManagementWidget(); + case 4: + return const StatistiqueManageWidget(); default: return const Center(child: Text("Page non trouvée")); } diff --git a/frontend/lib/services/auth_service.dart b/frontend/lib/services/auth_service.dart index f2b87a6..bef82ba 100644 --- a/frontend/lib/services/auth_service.dart +++ b/frontend/lib/services/auth_service.dart @@ -112,40 +112,51 @@ class AuthService { } } - /*static const String _usersKey = 'users'; - static const String _parentsKey = 'parents'; - static const String _childrenKey = 'children'; + Future getUserId() async { + final token = await TokenService.getToken(); + if (token == null) return ''; - // Méthode pour se connecter (mode démonstration) - static Future login(String email, String password) async { - await Future.delayed(const Duration(seconds: 1)); // Simule un délai de traitement - throw Exception('Mode démonstration - Connexion désactivée'); + try { + final parts = token.split('.'); + if (parts.length != 3) return ''; + + final payload = parts[1]; + final normalizedPayload = base64Url.normalize(payload); + final decoded = utf8.decode(base64Url.decode(normalizedPayload)); + final Map payloadMap = jsonDecode(decoded); + + return payloadMap['sub'] ?? ''; + } catch (e) { + print('Error extracting user id from token: $e'); + return ''; + } } - // Méthode pour s'inscrire (mode démonstration) - static Future register({ - required String email, - required String password, - required String firstName, - required String lastName, - required String role, - }) async { - await Future.delayed(const Duration(seconds: 1)); // Simule un délai de traitement - throw Exception('Mode démonstration - Inscription désactivée'); - } + Future getUserNameById() async { + final userid = await getUserId(); + final token = await TokenService.getToken(); - // Méthode pour se déconnecter (mode démonstration) - static Future logout() async { - // Ne fait rien en mode démonstration + if (token == null || userid.isEmpty) return null; + try { + final response = await http.get( + Uri.parse('$baseUrl${ApiConfig.users}/$userid'), + headers: { + 'Authorization': 'Bearer $token', + 'accept': '*/*', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final firstName = data['prenom']; + // final lastName = data['nom']; + return '$firstName'; + } else { + print('Erreur Api: ${response.statusCode} - ${response.body}'); + return null; + } + } catch (e) { + print('Error fetching user name: $e'); + return null; + } } - - // Méthode pour vérifier si l'utilisateur est connecté (mode démonstration) - static Future isLoggedIn() async { - return false; // Toujours non connecté en mode démonstration - } - - // Méthode pour récupérer l'utilisateur connecté (mode démonstration) - static Future getCurrentUser() async { - return null; // Aucun utilisateur en mode démonstration - }*/ -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/lib/widgets/admin/DashboardSidebarAdmin.dart b/frontend/lib/widgets/admin/DashboardSidebarAdmin.dart new file mode 100644 index 0000000..835eb98 --- /dev/null +++ b/frontend/lib/widgets/admin/DashboardSidebarAdmin.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +class DashboardSidebarAdmin extends StatelessWidget { + final int selectedIndex; + final ValueChanged onTabChange; + + const DashboardSidebarAdmin({ + Key? key, + required this.selectedIndex, + required this.onTabChange, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final items = [ + {'title': 'Gestionnaires', 'icon': Icons.admin_panel_settings}, + {'title': 'Parents', 'icon': Icons.family_restroom}, + {'title': 'Assistantes maternelles', 'icon': Icons.woman}, + {'title': 'Administrateurs', 'icon': Icons.supervisor_account}, + {'title': 'Statistiques', 'icon': Icons.bar_chart}, + ]; + + return Container( + width: 250, + color: const Color(0xFFF7F7F7), + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Avatar en haut + Center( + child: Column( + children: const [ + CircleAvatar(radius: 32, child: Icon(Icons.person, size: 32)), + SizedBox(height: 8), + Text("Admin", style: TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + const SizedBox(height: 32), + const Text("Navigation", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + + // Navigation + ...List.generate(items.length, (index) { + final item = items[index]; + final isActive = index == selectedIndex; + + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: ListTile( + tileColor: isActive ? const Color(0xFF9CC5C0) : null, + leading: Icon(item['icon'] as IconData, color: isActive ? Color(0xFF9CC5C0) : Colors.black54), + title: Text( + item['title'] as String, + style: TextStyle( + color: isActive ? Color(0xFF9CC5C0) : Colors.black, + fontWeight: isActive ? FontWeight.bold : FontWeight.normal, + ), + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + onTap: () => onTabChange(index), + ), + ); + }), + ], + ), + ); + } +} diff --git a/frontend/lib/widgets/admin/Statistique_manage_widget.dart b/frontend/lib/widgets/admin/Statistique_manage_widget.dart new file mode 100644 index 0000000..12e75f6 --- /dev/null +++ b/frontend/lib/widgets/admin/Statistique_manage_widget.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class StatistiqueManageWidget extends StatelessWidget { + const StatistiqueManageWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + 'Statistiques', + style: Theme.of(context).textTheme.headlineMedium, + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/widgets/admin/admin_manage_widget.dart b/frontend/lib/widgets/admin/admin_manage_widget.dart new file mode 100644 index 0000000..94660a0 --- /dev/null +++ b/frontend/lib/widgets/admin/admin_manage_widget.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:p_tits_pas/widgets/admin/base_user_management.dart'; + +class AdministrateurManagementWidget extends StatelessWidget { + const AdministrateurManagementWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BaseUserManagementWidget( + config: UserDisplayConfig( + title: 'Administrateur', + role: 'administrateur', + defaultIcon: Icons.admin_panel_settings, + filterFields: [ + FilterField( + label: 'Rechercher', + hint: 'Nom ou email', + type: FilterType.text, + filter: (user, query) { + final fullName = + '${user['firstName'] ?? ''} ${user['lastName'] ?? ''}' + .toLowerCase(); + final email = (user['email'] ?? '').toLowerCase(); + return fullName.contains(query.toLowerCase()) || + email.contains(query.toLowerCase()); + }, + ), + FilterField( + label: 'Statut', + hint: 'Tous', + type: FilterType.dropdown, + options: ['actif', 'en_attente', 'inactif', 'supprimé'], + filter: (user, status) { + if (status.isEmpty) return true; + return user['statut']?.toString().toLowerCase() == + status.toLowerCase(); + }, + ), + ], + actions: [ + const UserAction( + icon: Icons.edit, + color: Colors.orange, + tooltip: 'Modifier', + onPressed: _editAdministrateur, + ), + const UserAction( + icon: Icons.security, + color: Colors.blue, + tooltip: 'Gérer droits', + onPressed: _manageRights, + ), + const UserAction( + icon: Icons.lock_reset, + color: Colors.purple, + tooltip: 'Réinitialiser MDP', + onPressed: _resetPassword, + ), + const UserAction( + icon: Icons.toggle_on, + color: Colors.green, + tooltip: 'Activer/Désactiver', + onPressed: _toggleStatus, + ), + const UserAction( + icon: Icons.delete, + color: Colors.red, + tooltip: 'Supprimer', + onPressed: _deleteAdministrateur, + ), + ], + getDisplayName: (user) => '${user['prenom'] ?? ''} ${user['nom'] ?? ''}', + getSubtitle: (user) { + final email = user['email'] ?? ''; + final profession = user['profession'] ?? 'Non spécifiée'; + final ville = user['ville'] ?? ''; + // final statut = AdministrateurService.getStatutDisplay(user['statut']); + final statut = user['statut'] ?? 'inactif'; + final changementMdp = + user['changement_mdp_obligatoire'] == true ? 'MDP à changer' : ''; + + return '$email\n$profession${ville.isNotEmpty ? ' • $ville' : ''}\nStatut: $statut ${changementMdp.isNotEmpty ? '• $changementMdp' : ''}'; + }, + )); + } + + static Future _editAdministrateur( + BuildContext context, Map user) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Modifier administrateur: ${user['prenom']} ${user['nom']}')), + ); + } + + static Future _manageRights( + BuildContext context, Map user) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Gérer droits pour: ${user['prenom']} ${user['nom']}')), + ); + } + + static Future _resetPassword( + BuildContext context, Map user) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Réinitialiser mot de passe pour: ${user['prenom']} ${user['nom']}')), + ); + } + + static Future _toggleStatus( + BuildContext context, Map user) async { + final currentStatus = user['statut'] ?? 'inactif'; + final newStatus = currentStatus == 'actif' ? 'inactif' : 'actif'; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${newStatus == 'actif' ? 'Activer' : 'Désactiver'} administrateur: ${user['prenom']} ${user['nom']}')), + ); + } + + static Future _deleteAdministrateur( + BuildContext context, Map user) async { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Supprimer administrateur: ${user['prenom']} ${user['nom']}')), + ); + } +} diff --git a/frontend/lib/widgets/admin/assistante_maternelle_management_widget.dart b/frontend/lib/widgets/admin/assistante_maternelle_management_widget.dart index b452f2b..5eab76f 100644 --- a/frontend/lib/widgets/admin/assistante_maternelle_management_widget.dart +++ b/frontend/lib/widgets/admin/assistante_maternelle_management_widget.dart @@ -47,7 +47,7 @@ class AssistanteMaternelleManagementWidget extends StatelessWidget { label: 'Statut', hint: 'Tous', type: FilterType.dropdown, - options: ['actif', 'en attente', 'inactif'], + options: ['actif', 'en_attente', 'inactif'], filter: (user, status) { if (status.isEmpty) return true; return user['statut']?.toString().toLowerCase() == status.toLowerCase(); @@ -55,19 +55,19 @@ class AssistanteMaternelleManagementWidget extends StatelessWidget { ), ], actions: [ - UserAction( + const UserAction( icon: Icons.edit, color: Colors.orange, tooltip: 'Modifier', onPressed: _editAssistante, ), - UserAction( + const UserAction( icon: Icons.delete, color: Colors.red, tooltip: 'Supprimer', onPressed: _deleteAssistante, ), - UserAction( + const UserAction( icon: Icons.location_on, color: Colors.green, tooltip: 'Voir zone', diff --git a/frontend/lib/widgets/admin/base_user_management.dart b/frontend/lib/widgets/admin/base_user_management.dart index 3567dbf..61c008c 100644 --- a/frontend/lib/widgets/admin/base_user_management.dart +++ b/frontend/lib/widgets/admin/base_user_management.dart @@ -140,7 +140,7 @@ class _BaseUserManagementWidgetState extends State { switch (status.toString().toLowerCase()) { case 'actif': return 'Actif'; - case 'en attente': + case 'en_attente': return 'En attente'; case 'inactif': return 'Inactif'; @@ -156,7 +156,7 @@ class _BaseUserManagementWidgetState extends State { switch (status) { case 'actif': return Colors.green; - case 'en attente': + case 'en_attente': return Colors.orange; case 'inactif': return Colors.grey; diff --git a/frontend/lib/widgets/admin/dashboard_admin.dart b/frontend/lib/widgets/admin/dashboard_admin.dart index 6f63760..a038c1e 100644 --- a/frontend/lib/widgets/admin/dashboard_admin.dart +++ b/frontend/lib/widgets/admin/dashboard_admin.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:p_tits_pas/services/auth_service.dart'; class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidget { final int selectedIndex; @@ -6,6 +7,29 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge const DashboardAppBarAdmin({Key? key, required this.selectedIndex, required this.onTabChange}) : super(key: key); + void _Logout(BuildContext context) async { + try { + final authS = AuthService(); + await authS.logout(); + Navigator.of(context).pushReplacementNamed('/login'); + } catch (e) { + print('Erreur lors de la déconnexion: $e'); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erreur lors de la déconnexion'), + backgroundColor: Colors.red, + ), + ); + } + } + + Future _getUserName() async { + final authS = AuthService(); + final userName = await authS.getUserNameById(); + return userName ?? 'Admin'; + } + + @override Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10); @@ -29,33 +53,36 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge SizedBox(width: MediaQuery.of(context).size.width * 0.1), // Navigation principale - _buildNavItem(context, 'Gestionnaires', 0), - const SizedBox(width: 24), - _buildNavItem(context, 'Parents', 1), - const SizedBox(width: 24), - _buildNavItem(context, 'Assistantes maternelles', 2), - const SizedBox(width: 24), - _buildNavItem(context, 'Administrateurs', 3), + // _buildNavItem(context, 'Gestionnaires', 0), + // const SizedBox(width: 24), + // _buildNavItem(context, 'Parents', 1), + // const SizedBox(width: 24), + // _buildNavItem(context, 'Assistantes maternelles', 2), + // const SizedBox(width: 24), + // _buildNavItem(context, 'Administrateurs', 4), ], ), actions: isMobile ? [_buildMobileMenu(context)] : [ // Nom de l'utilisateur - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), child: Center( - child: Text( - 'Admin', - style: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.w500, - ), + child: FutureBuilder( + future: _getUserName(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Text("Chargement..."); + } else if (snapshot.hasError) { + return const Text("Erreur"); + } else { + return Text(snapshot.data ?? "Admin"); + } + } ), ), ), - // Bouton déconnexion Padding( padding: const EdgeInsets.only(right: 16), @@ -77,28 +104,28 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge ); } - Widget _buildNavItem(BuildContext context, String title, int index) { - final bool isActive = index == selectedIndex; - return InkWell( - onTap: () => onTabChange(index), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent, - borderRadius: BorderRadius.circular(20), - border: isActive ? null : Border.all(color: Colors.black26), - ), - child: Text( - title, - style: TextStyle( - color: isActive ? Colors.white : Colors.black, - fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, - fontSize: 14, - ), - ), - ), - ); -} +// Widget _buildNavItem(BuildContext context, String title, int index) { +// final bool isActive = index == selectedIndex; +// return InkWell( +// onTap: () => onTabChange(index), +// child: Container( +// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), +// decoration: BoxDecoration( +// color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent, +// borderRadius: BorderRadius.circular(20), +// border: isActive ? null : Border.all(color: Colors.black26), +// ), +// child: Text( +// title, +// style: TextStyle( +// color: isActive ? Colors.white : Colors.black, +// fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, +// fontSize: 14, +// ), +// ), +// ), +// ); +// } Widget _buildMobileMenu(BuildContext context) { @@ -133,8 +160,8 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge ), ElevatedButton( onPressed: () { - Navigator.pop(context); - // TODO: Implémenter la logique de déconnexion + // Navigator.pop(context); + _Logout(context); }, child: const Text('Déconnecter'), ), diff --git a/frontend/lib/widgets/admin/parent_managmant_widget.dart b/frontend/lib/widgets/admin/parent_managmant_widget.dart index f9e0920..f2f32d2 100644 --- a/frontend/lib/widgets/admin/parent_managmant_widget.dart +++ b/frontend/lib/widgets/admin/parent_managmant_widget.dart @@ -1,118 +1,98 @@ import 'package:flutter/material.dart'; import 'package:p_tits_pas/services/user_service.dart'; +import 'package:p_tits_pas/widgets/admin/base_user_management.dart'; -class ParentManagementWidget extends StatefulWidget { +class ParentManagementWidget extends StatelessWidget { const ParentManagementWidget({super.key}); @override - State createState() => _ParentManagementWidgetState(); -} - -class _ParentManagementWidgetState extends State { - - final UserService _userService = UserService(); - final TextEditingController _searchController = TextEditingController(); - - List> _allParents = []; - List> _filteredParents = []; - String? _selectedStatus; - bool _isLoading = true; - String? _error; - - - @override - void initState() { - super.initState(); - _loadParents(); + Widget build(BuildContext context) { + return BaseUserManagementWidget( + config: UserDisplayConfig( + title: 'Gestion des Parents', + role: 'parent', + defaultIcon: Icons.person_outline, + filterFields: [ + FilterField( + label: 'Rechercher', + hint: 'Nom ou email', + type: FilterType.text, + filter: (user, query) { + final fullName = '${user['prenom'] ?? ''} ${user['nom'] ?? ''}'.toLowerCase(); + final email = (user['email'] ?? '').toLowerCase(); + return fullName.contains(query.toLowerCase()) || + email.contains(query.toLowerCase()); + }, + ), + FilterField( + label: 'Statut', + hint: 'Tous', + type: FilterType.dropdown, + options: ['actif', 'en_attente', 'inactif', 'supprimé'], + filter: (user, status) { + if (status.isEmpty) return true; + return user['statut']?.toString().toLowerCase() == status.toLowerCase(); + }, + ), + FilterField( + label: 'Nombre d\'enfants', + hint: 'Minimum', + type: FilterType.number, + filter: (user, query) { + final nombreEnfants = int.tryParse(user['nombreEnfants']?.toString() ?? '0') ?? 0; + final minEnfants = int.tryParse(query) ?? 0; + return nombreEnfants >= minEnfants; + }, + ), + ], + actions: [ + const UserAction( + icon: Icons.edit, + color: Colors.orange, + tooltip: 'Modifier', + onPressed: _editParent, + ), + const UserAction( + icon: Icons.delete, + color: Colors.red, + tooltip: 'Supprimer', + onPressed: _deleteParent, + ), + const UserAction( + icon: Icons.child_care, + color: Colors.purple, + tooltip: 'Voir enfants', + onPressed: _showChildren, + ), + ], + getDisplayName: (user) => '${user['prenom'] ?? ''} ${user['nom'] ?? ''}', + getSubtitle: (user) { + final email = user['email'] ?? ''; + final nombreEnfants = user['nombreEnfants'] ?? user['children']?.length ?? 0; + return '$email\nNombre d\'enfants: $nombreEnfants'; + }, + ), + ); } - @override - void dispose() { - _searchController.dispose(); - super.dispose(); + static Future _editParent(BuildContext context, Map parent) async { + // TODO: Implémenter l'édition + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Fonctionnalité de modification à implémenter'), + backgroundColor: Colors.orange, + ), + ); } - Future _loadParents() async { - setState(() { - _isLoading = true; - _error = null; - }); - - try { - final parents = await _userService.getUsersByRole("parent"); - setState(() { - _allParents = parents; - _filteredParents = parents; - _isLoading = false; - }); - } catch (e) { - setState(() { - _error = e.toString(); - _isLoading = false; - }); - } - } - - void _applyFilters() { - setState(() { - _filteredParents = _allParents.where((parent) { - final searchQuery = _searchController.text.toLowerCase(); - final fullName = '${parent['prenom'] ?? ''} ${parent['nom'] ?? ''}'.toLowerCase(); - final email = (parent['email'] ?? '').toLowerCase(); - - bool matchesSearch = searchQuery.isEmpty || - fullName.contains(searchQuery) || - email.contains(searchQuery); - - bool matchesStatus = _selectedStatus == null || - _selectedStatus!.isEmpty || - parent['statut']?.toString() == _selectedStatus; - - return matchesSearch && matchesStatus; - }).toList(); - }); - } - - String _getStatusDisplay(Map parent) { - final status = parent['statut']; - if (status == null) return 'Non défini'; - switch (status.toString().toLowerCase()) { - case 'actif': - return 'Actif'; - case 'en_attente': - return 'En attente'; - case 'inactif': - return 'Inactif'; - case 'supprimé': - return 'Supprimé'; - default: - return status.toString(); - } - } - - Color _getStatusColor(Map parent) { - final status = parent['statut']?.toString().toLowerCase(); - switch (status) { - case 'actif': - return Colors.green; - case 'en_attente': - return Colors.orange; - case 'inactif': - return Colors.grey; - case 'supprimé': - return Colors.red; - default: - return Colors.grey; - } - } - - Future _confirmDelete(Map parent) async { + static Future _deleteParent(BuildContext context, Map parent) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: Text( - 'Êtes-vous sûr de vouloir supprimer le compte de ${parent['firstName']} ${parent['lastName']} ?' + 'Êtes-vous sûr de vouloir supprimer le compte de ${parent['prenom']} ${parent['nom']} ?\n\n' + 'Cette action supprimera également tous les contrats et données associés.' ), actions: [ TextButton( @@ -129,276 +109,87 @@ class _ParentManagementWidgetState extends State { ); if (confirmed == true) { - await _deleteParent(parent); - } - } - - Future _deleteParent(Map parent) async { - try { - final success = await _userService.deleteUser(parent['id']); - if (success) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${parent['firstName']} ${parent['lastName']} supprimé avec succès'), - backgroundColor: Colors.green, - ), - ); - _loadParents(); // Recharger la liste - } else { - throw Exception('Erreur lors de la suppression'); + try { + final userService = UserService(); + final success = await userService.deleteUser(parent['id']); + + if (success && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('${parent['prenom']} ${parent['nom']} supprimé avec succès'), + backgroundColor: Colors.green, + ), + ); + } else { + throw Exception('Erreur lors de la suppression'); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Erreur: ${e.toString()}'), + backgroundColor: Colors.red, + ), + ); + } } - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Erreur: ${e.toString()}'), - backgroundColor: Colors.red, - ), - ); } } - void _viewParentDetails(Map parent) { + static Future _showChildren(BuildContext context, Map parent) async { + final children = parent['children'] as List? ?? []; + showDialog( context: context, builder: (context) => AlertDialog( - title: Text('${parent['firstName']} ${parent['lastName']}'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Email: ${parent['email']}'), - Text('Rôle: ${parent['role']}'), - Text('Statut: ${_getStatusDisplay(parent)}'), - Text('ID: ${parent['id']}'), - if (parent['createdAt'] != null) - Text('Créé le: ${DateTime.parse(parent['createdAt']).toLocal().toString().split(' ')[0]}'), - ], + title: Text('Enfants de ${parent['prenom']} ${parent['nom']}'), + content: Container( + width: double.maxFinite, + constraints: const BoxConstraints(maxHeight: 400), + child: children.isEmpty + ? const Text('Aucun enfant enregistré') + : SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: children.map((child) => Card( + child: ListTile( + leading: const Icon(Icons.child_care), + title: Text(child['prenom'] ?? child['firstName'] ?? 'Nom non défini'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (child['dateNaissance'] != null || child['birthDate'] != null) + Text('Né(e) le: ${child['dateNaissance'] ?? child['birthDate']}'), + if (child['age'] != null) + Text('Âge: ${child['age']} ans'), + ], + ), + isThreeLine: true, + ), + )).toList(), + ), + ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), + if (children.isNotEmpty) + TextButton( + onPressed: () { + Navigator.of(context).pop(); + // TODO: Naviguer vers la gestion des enfants + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Navigation vers la gestion des enfants à implémenter'), + ), + ); + }, + child: const Text('Gérer les enfants'), + ), ], ), ); } - - - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Gestion des Parents (${_filteredParents.length})', - style: Theme.of(context).textTheme.headlineSmall, - ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: _loadParents, - tooltip: 'Actualiser', - ), - ], - ), - const SizedBox(height: 16), - _buildSearchSection(), - const SizedBox(height: 16), - Expanded( - child: _buildParentsList(), - ), - ], - )); - } - - Widget _buildSearchSection() { - return Wrap( - spacing: 16, - runSpacing: 8, - children: [ - SizedBox( - width: 250, - child: TextField( - controller: _searchController, - decoration: const InputDecoration( - labelText: "Rechercher un parent", - hintText: "Nom ou email", - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.search), - ), - onChanged: (value) => _applyFilters(), - ), - ), - SizedBox( - width: 200, - child: DropdownButtonFormField( - value: _selectedStatus, - decoration: const InputDecoration( - labelText: "Statut", - border: OutlineInputBorder(), - ), - items: const [ - DropdownMenuItem(value: null, child: Text("Tous")), - DropdownMenuItem(value: "active", child: Text("Actif")), - DropdownMenuItem(value: "pending", child: Text("En attente")), - DropdownMenuItem(value: "inactive", child: Text("Inactif")), - DropdownMenuItem(value: "deleted", child: Text("Supprimé")), - ], - onChanged: (value) { - setState(() { - _selectedStatus = value; - }); - _applyFilters(); - }, - ), - ), - ], - ); - } - - Widget _buildParentsList() { - if (_isLoading) { - return const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 16), - Text('Chargement des parents...'), - ], - ), - ); - } - - if (_error != null) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.error_outline, size: 48, color: Colors.red[300]), - const SizedBox(height: 16), - Text( - 'Erreur de chargement', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text( - _error!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _loadParents, - child: const Text('Réessayer'), - ), - ], - ), - ); - } - - if (_filteredParents.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.people_outline, size: 48, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - _allParents.isEmpty ? 'Aucun parent trouvé' : 'Aucun résultat', - style: Theme.of(context).textTheme.titleLarge, - ), - if (_allParents.isNotEmpty) ...[ - const SizedBox(height: 8), - const Text('Essayez de modifier vos critères de recherche'), - ], - ], - ), - ); - } - - return ListView.builder( - itemCount: _filteredParents.length, - itemBuilder: (context, index) { - final parent = _filteredParents[index]; - return Card( - margin: const EdgeInsets.symmetric(vertical: 4), - child: ListTile( - leading: CircleAvatar( - backgroundColor: _getStatusColor(parent).withOpacity(0.2), - child: Text( - '${parent['firstName']?[0] ?? ''}${parent['lastName']?[0] ?? ''}', - style: TextStyle( - color: _getStatusColor(parent), - fontWeight: FontWeight.bold, - ), - ), - ), - title: Text('${parent['firstName'] ?? ''} ${parent['lastName'] ?? ''}'), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(parent['email'] ?? ''), - const SizedBox(height: 4), - Row( - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: _getStatusColor(parent).withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: _getStatusColor(parent)), - ), - child: Text( - _getStatusDisplay(parent), - style: TextStyle( - color: _getStatusColor(parent), - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ], - ), - isThreeLine: true, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.visibility, color: Colors.blue), - tooltip: "Voir détails", - onPressed: () => _viewParentDetails(parent), - ), - IconButton( - icon: const Icon(Icons.edit, color: Colors.orange), - tooltip: "Modifier", - onPressed: () { - // TODO: Implémenter la modification - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Fonctionnalité de modification à implémenter'), - ), - ); - }, - ), - IconButton( - icon: const Icon(Icons.delete, color: Colors.red), - tooltip: "Supprimer", - onPressed: () => _confirmDelete(parent), - ), - ], - ), - ), - ); - }, - ); - } - - -} +} \ No newline at end of file diff --git a/frontend/lib/widgets/dashbord_parent/dashboard_app_bar.dart b/frontend/lib/widgets/dashbord_parent/dashboard_app_bar.dart index c89fd6e..6dfe319 100644 --- a/frontend/lib/widgets/dashbord_parent/dashboard_app_bar.dart +++ b/frontend/lib/widgets/dashbord_parent/dashboard_app_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:p_tits_pas/services/auth_service.dart'; class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { final int selectedIndex; @@ -9,6 +10,29 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { @override Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10); + Future _getUserName() async { + final authS = AuthService(); + final userName = await authS.getUserNameById(); + return userName ?? 'Bienvenue'; + } + + void _logout (BuildContext context) { + try { + final authS = AuthService(); + authS.logout(); + Navigator.of(context).pushReplacementNamed('/login'); + } catch (e) { + print('Erreur lors de la déconnexion: $e'); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erreur lors de la déconnexion'), + backgroundColor: Colors.red, + ), + ); + } + + } + @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width < 768; @@ -17,20 +41,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { elevation: 0, title: Row( children: [ - // Logo de la ville - // Container( - // height: 32, - // width: 32, - // decoration: BoxDecoration( - // color: Colors.white, - // borderRadius: BorderRadius.circular(8), - // ), - // child: const Icon( - // Icons.location_city, - // color: Color(0xFF9CC5C0), - // size: 20, - // ), - // ), SizedBox(width: MediaQuery.of(context).size.width * 0.19), const Text( "P'tit Pas", @@ -54,16 +64,20 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { ? [_buildMobileMenu(context)] : [ // Nom de l'utilisateur - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), child: Center( - child: Text( - 'Jean Dupont', - style: TextStyle( - color: Colors.black, - fontSize: 16, - fontWeight: FontWeight.w500, - ), + child: FutureBuilder( + future: _getUserName(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Text("Chargement..."); + } else if (snapshot.hasError) { + return const Text("Erreur"); + } else { + return Text(snapshot.data ?? "Admin"); + } + } ), ), ), @@ -145,6 +159,7 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { ElevatedButton( onPressed: () { Navigator.pop(context); + _logout(context); // TODO: Implémenter la logique de déconnexion }, child: const Text('Déconnecter'), diff --git a/frontend/lib/widgets/dashbord_parent/wid_dashbord.dart b/frontend/lib/widgets/dashbord_parent/wid_dashbord.dart index 6ed87cf..083d643 100644 --- a/frontend/lib/widgets/dashbord_parent/wid_dashbord.dart +++ b/frontend/lib/widgets/dashbord_parent/wid_dashbord.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:p_tits_pas/widgets/dashbord_parent/ChildrenSidebarwidget.dart'; -import 'package:p_tits_pas/widgets/dashbord_parent/children_sidebar.dart'; import 'package:p_tits_pas/widgets/dashbord_parent/wid_mainContentArea.dart'; -import 'package:p_tits_pas/widgets/messaging_sidebar.dart'; Widget Dashbord_body() { return Row( -- 2.47.2