Add SidebarAdmin

This commit is contained in:
Hanim 2025-09-19 15:44:12 +02:00
parent 70033dd9c3
commit e0083d665b
11 changed files with 561 additions and 482 deletions

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart'; 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/assistante_maternelle_management_widget.dart';
import 'package:p_tits_pas/widgets/admin/gestionnaire_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'; import 'package:p_tits_pas/widgets/admin/parent_managmant_widget.dart';
@ -24,41 +27,58 @@ class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(60.0), preferredSize: const Size.fromHeight(60.0),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(color: Colors.grey.shade300), bottom: BorderSide(color: Colors.grey.shade300),
),
),
child: DashboardAppBarAdmin(
selectedIndex: selectedIndex,
onTabChange: onTabChange,
), ),
), ),
child: DashboardAppBarAdmin(
selectedIndex: selectedIndex,
onTabChange: onTabChange,
),
), ),
), body: Column(
body: Column( children: [
children: [ Expanded(
Expanded( child: Row(
child: _getBody(), crossAxisAlignment: CrossAxisAlignment.stretch,
), children: [
const AppFooter(), SizedBox(
], width: 250,
), child: DashboardSidebarAdmin(
); selectedIndex: selectedIndex,
onTabChange: onTabChange,
),
),
Expanded(
flex: 2,
child: _getBody(),
),
],
),
),
const AppFooter(),
],
),
);
} }
Widget _getBody() { Widget _getBody() {
switch (selectedIndex) { switch (selectedIndex) {
case 0: case 0:
return const GestionnaireManagementWidget(); return const GestionnaireManagementWidget();
case 1: case 1:
return const ParentManagementWidget(); return const ParentManagementWidget();
case 2: case 2:
return const AssistanteMaternelleManagementWidget(); return const AssistanteMaternelleManagementWidget();
case 3: case 3:
return const Center(child: Text("👨‍💼 Administrateurs")); return const AdministrateurManagementWidget();
case 4:
return const StatistiqueManageWidget();
default: default:
return const Center(child: Text("Page non trouvée")); return const Center(child: Text("Page non trouvée"));
} }

View File

@ -112,40 +112,51 @@ class AuthService {
} }
} }
/*static const String _usersKey = 'users'; Future<String> getUserId() async {
static const String _parentsKey = 'parents'; final token = await TokenService.getToken();
static const String _childrenKey = 'children'; if (token == null) return '';
// Méthode pour se connecter (mode démonstration) try {
static Future<AppUser> login(String email, String password) async { final parts = token.split('.');
await Future.delayed(const Duration(seconds: 1)); // Simule un délai de traitement if (parts.length != 3) return '';
throw Exception('Mode démonstration - Connexion désactivée');
final payload = parts[1];
final normalizedPayload = base64Url.normalize(payload);
final decoded = utf8.decode(base64Url.decode(normalizedPayload));
final Map<String, dynamic> 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) Future<String?> getUserNameById() async {
static Future<AppUser> register({ final userid = await getUserId();
required String email, final token = await TokenService.getToken();
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');
}
// Méthode pour se déconnecter (mode démonstration) if (token == null || userid.isEmpty) return null;
static Future<void> logout() async { try {
// Ne fait rien en mode démonstration 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<bool> 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<AppUser?> getCurrentUser() async {
return null; // Aucun utilisateur en mode démonstration
}*/
}

View File

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
class DashboardSidebarAdmin extends StatelessWidget {
final int selectedIndex;
final ValueChanged<int> 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),
),
);
}),
],
),
);
}
}

View File

@ -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,
),
);
}
}

View File

@ -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<void> _editAdministrateur(
BuildContext context, Map<String, dynamic> user) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Modifier administrateur: ${user['prenom']} ${user['nom']}')),
);
}
static Future<void> _manageRights(
BuildContext context, Map<String, dynamic> user) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gérer droits pour: ${user['prenom']} ${user['nom']}')),
);
}
static Future<void> _resetPassword(
BuildContext context, Map<String, dynamic> user) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Réinitialiser mot de passe pour: ${user['prenom']} ${user['nom']}')),
);
}
static Future<void> _toggleStatus(
BuildContext context, Map<String, dynamic> 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<void> _deleteAdministrateur(
BuildContext context, Map<String, dynamic> user) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Supprimer administrateur: ${user['prenom']} ${user['nom']}')),
);
}
}

View File

@ -47,7 +47,7 @@ class AssistanteMaternelleManagementWidget extends StatelessWidget {
label: 'Statut', label: 'Statut',
hint: 'Tous', hint: 'Tous',
type: FilterType.dropdown, type: FilterType.dropdown,
options: ['actif', 'en attente', 'inactif'], options: ['actif', 'en_attente', 'inactif'],
filter: (user, status) { filter: (user, status) {
if (status.isEmpty) return true; if (status.isEmpty) return true;
return user['statut']?.toString().toLowerCase() == status.toLowerCase(); return user['statut']?.toString().toLowerCase() == status.toLowerCase();
@ -55,19 +55,19 @@ class AssistanteMaternelleManagementWidget extends StatelessWidget {
), ),
], ],
actions: [ actions: [
UserAction( const UserAction(
icon: Icons.edit, icon: Icons.edit,
color: Colors.orange, color: Colors.orange,
tooltip: 'Modifier', tooltip: 'Modifier',
onPressed: _editAssistante, onPressed: _editAssistante,
), ),
UserAction( const UserAction(
icon: Icons.delete, icon: Icons.delete,
color: Colors.red, color: Colors.red,
tooltip: 'Supprimer', tooltip: 'Supprimer',
onPressed: _deleteAssistante, onPressed: _deleteAssistante,
), ),
UserAction( const UserAction(
icon: Icons.location_on, icon: Icons.location_on,
color: Colors.green, color: Colors.green,
tooltip: 'Voir zone', tooltip: 'Voir zone',

View File

@ -140,7 +140,7 @@ class _BaseUserManagementWidgetState extends State<BaseUserManagementWidget> {
switch (status.toString().toLowerCase()) { switch (status.toString().toLowerCase()) {
case 'actif': case 'actif':
return 'Actif'; return 'Actif';
case 'en attente': case 'en_attente':
return 'En attente'; return 'En attente';
case 'inactif': case 'inactif':
return 'Inactif'; return 'Inactif';
@ -156,7 +156,7 @@ class _BaseUserManagementWidgetState extends State<BaseUserManagementWidget> {
switch (status) { switch (status) {
case 'actif': case 'actif':
return Colors.green; return Colors.green;
case 'en attente': case 'en_attente':
return Colors.orange; return Colors.orange;
case 'inactif': case 'inactif':
return Colors.grey; return Colors.grey;

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/services/auth_service.dart';
class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidget { class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidget {
final int selectedIndex; 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); 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<String> _getUserName() async {
final authS = AuthService();
final userName = await authS.getUserNameById();
return userName ?? 'Admin';
}
@override @override
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10); 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), SizedBox(width: MediaQuery.of(context).size.width * 0.1),
// Navigation principale // Navigation principale
_buildNavItem(context, 'Gestionnaires', 0), // _buildNavItem(context, 'Gestionnaires', 0),
const SizedBox(width: 24), // const SizedBox(width: 24),
_buildNavItem(context, 'Parents', 1), // _buildNavItem(context, 'Parents', 1),
const SizedBox(width: 24), // const SizedBox(width: 24),
_buildNavItem(context, 'Assistantes maternelles', 2), // _buildNavItem(context, 'Assistantes maternelles', 2),
const SizedBox(width: 24), // const SizedBox(width: 24),
_buildNavItem(context, 'Administrateurs', 3), // _buildNavItem(context, 'Administrateurs', 4),
], ],
), ),
actions: isMobile actions: isMobile
? [_buildMobileMenu(context)] ? [_buildMobileMenu(context)]
: [ : [
// Nom de l'utilisateur // Nom de l'utilisateur
const Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center( child: Center(
child: Text( child: FutureBuilder<String>(
'Admin', future: _getUserName(),
style: TextStyle( builder: (context, snapshot) {
color: Colors.black, if (snapshot.connectionState == ConnectionState.waiting) {
fontSize: 16, return const Text("Chargement...");
fontWeight: FontWeight.w500, } else if (snapshot.hasError) {
), return const Text("Erreur");
} else {
return Text(snapshot.data ?? "Admin");
}
}
), ),
), ),
), ),
// Bouton déconnexion // Bouton déconnexion
Padding( Padding(
padding: const EdgeInsets.only(right: 16), padding: const EdgeInsets.only(right: 16),
@ -77,28 +104,28 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
); );
} }
Widget _buildNavItem(BuildContext context, String title, int index) { // Widget _buildNavItem(BuildContext context, String title, int index) {
final bool isActive = index == selectedIndex; // final bool isActive = index == selectedIndex;
return InkWell( // return InkWell(
onTap: () => onTabChange(index), // onTap: () => onTabChange(index),
child: Container( // child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( // decoration: BoxDecoration(
color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent, // color: isActive ? const Color(0xFF9CC5C0) : Colors.transparent,
borderRadius: BorderRadius.circular(20), // borderRadius: BorderRadius.circular(20),
border: isActive ? null : Border.all(color: Colors.black26), // border: isActive ? null : Border.all(color: Colors.black26),
), // ),
child: Text( // child: Text(
title, // title,
style: TextStyle( // style: TextStyle(
color: isActive ? Colors.white : Colors.black, // color: isActive ? Colors.white : Colors.black,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, // fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
fontSize: 14, // fontSize: 14,
), // ),
), // ),
), // ),
); // );
} // }
Widget _buildMobileMenu(BuildContext context) { Widget _buildMobileMenu(BuildContext context) {
@ -133,8 +160,8 @@ class DashboardAppBarAdmin extends StatelessWidget implements PreferredSizeWidge
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pop(context); // Navigator.pop(context);
// TODO: Implémenter la logique de déconnexion _Logout(context);
}, },
child: const Text('Déconnecter'), child: const Text('Déconnecter'),
), ),

View File

@ -1,118 +1,98 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/services/user_service.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}); const ParentManagementWidget({super.key});
@override @override
State<ParentManagementWidget> createState() => _ParentManagementWidgetState(); Widget build(BuildContext context) {
} return BaseUserManagementWidget(
config: UserDisplayConfig(
class _ParentManagementWidgetState extends State<ParentManagementWidget> { title: 'Gestion des Parents',
role: 'parent',
final UserService _userService = UserService(); defaultIcon: Icons.person_outline,
final TextEditingController _searchController = TextEditingController(); filterFields: [
FilterField(
List<Map<String, dynamic>> _allParents = []; label: 'Rechercher',
List<Map<String, dynamic>> _filteredParents = []; hint: 'Nom ou email',
String? _selectedStatus; type: FilterType.text,
bool _isLoading = true; filter: (user, query) {
String? _error; final fullName = '${user['prenom'] ?? ''} ${user['nom'] ?? ''}'.toLowerCase();
final email = (user['email'] ?? '').toLowerCase();
return fullName.contains(query.toLowerCase()) ||
@override email.contains(query.toLowerCase());
void initState() { },
super.initState(); ),
_loadParents(); 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 static Future<void> _editParent(BuildContext context, Map<String, dynamic> parent) async {
void dispose() { // TODO: Implémenter l'édition
_searchController.dispose(); ScaffoldMessenger.of(context).showSnackBar(
super.dispose(); const SnackBar(
content: Text('Fonctionnalité de modification à implémenter'),
backgroundColor: Colors.orange,
),
);
} }
Future <void> _loadParents() async { static Future<void> _deleteParent(BuildContext context, Map<String, dynamic> parent) 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<String, dynamic> 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<String, dynamic> 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<void> _confirmDelete(Map<String, dynamic> parent) async {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Confirmer la suppression'), title: const Text('Confirmer la suppression'),
content: Text( 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: [ actions: [
TextButton( TextButton(
@ -129,276 +109,87 @@ class _ParentManagementWidgetState extends State<ParentManagementWidget> {
); );
if (confirmed == true) { if (confirmed == true) {
await _deleteParent(parent); try {
} final userService = UserService();
} final success = await userService.deleteUser(parent['id']);
Future<void> _deleteParent(Map<String, dynamic> parent) async { if (success && context.mounted) {
try { ScaffoldMessenger.of(context).showSnackBar(
final success = await _userService.deleteUser(parent['id']); SnackBar(
if (success) { content: Text('${parent['prenom']} ${parent['nom']} supprimé avec succès'),
ScaffoldMessenger.of(context).showSnackBar( backgroundColor: Colors.green,
SnackBar( ),
content: Text('${parent['firstName']} ${parent['lastName']} supprimé avec succès'), );
backgroundColor: Colors.green, } else {
), throw Exception('Erreur lors de la suppression');
); }
_loadParents(); // Recharger la liste } catch (e) {
} else { if (context.mounted) {
throw Exception('Erreur lors de la suppression'); 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<String, dynamic> parent) { static Future<void> _showChildren(BuildContext context, Map<String, dynamic> parent) async {
final children = parent['children'] as List<dynamic>? ?? [];
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text('${parent['firstName']} ${parent['lastName']}'), title: Text('Enfants de ${parent['prenom']} ${parent['nom']}'),
content: Column( content: Container(
mainAxisSize: MainAxisSize.min, width: double.maxFinite,
crossAxisAlignment: CrossAxisAlignment.start, constraints: const BoxConstraints(maxHeight: 400),
children: [ child: children.isEmpty
Text('Email: ${parent['email']}'), ? const Text('Aucun enfant enregistré')
Text('Rôle: ${parent['role']}'), : SingleChildScrollView(
Text('Statut: ${_getStatusDisplay(parent)}'), child: Column(
Text('ID: ${parent['id']}'), mainAxisSize: MainAxisSize.min,
if (parent['createdAt'] != null) children: children.map((child) => Card(
Text('Créé le: ${DateTime.parse(parent['createdAt']).toLocal().toString().split(' ')[0]}'), 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: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'), 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<String>(
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),
),
],
),
),
);
},
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/services/auth_service.dart';
class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
final int selectedIndex; final int selectedIndex;
@ -9,6 +10,29 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
@override @override
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10); Size get preferredSize => const Size.fromHeight(kToolbarHeight + 10);
Future <String> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isMobile = MediaQuery.of(context).size.width < 768; final isMobile = MediaQuery.of(context).size.width < 768;
@ -17,20 +41,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
elevation: 0, elevation: 0,
title: Row( title: Row(
children: [ 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), SizedBox(width: MediaQuery.of(context).size.width * 0.19),
const Text( const Text(
"P'tit Pas", "P'tit Pas",
@ -54,16 +64,20 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
? [_buildMobileMenu(context)] ? [_buildMobileMenu(context)]
: [ : [
// Nom de l'utilisateur // Nom de l'utilisateur
const Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center( child: Center(
child: Text( child: FutureBuilder<String>(
'Jean Dupont', future: _getUserName(),
style: TextStyle( builder: (context, snapshot) {
color: Colors.black, if (snapshot.connectionState == ConnectionState.waiting) {
fontSize: 16, return const Text("Chargement...");
fontWeight: FontWeight.w500, } 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( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
_logout(context);
// TODO: Implémenter la logique de déconnexion // TODO: Implémenter la logique de déconnexion
}, },
child: const Text('Déconnecter'), child: const Text('Déconnecter'),

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:p_tits_pas/widgets/dashbord_parent/ChildrenSidebarwidget.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/dashbord_parent/wid_mainContentArea.dart';
import 'package:p_tits_pas/widgets/messaging_sidebar.dart';
Widget Dashbord_body() { Widget Dashbord_body() {
return Row( return Row(