You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

667 lines
23 KiB

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import '../Components/app_bar.dart';
import 'editUser.dart';
class ListUserPage extends StatefulWidget {
const ListUserPage({super.key});
@override
_ListUserPageState createState() => _ListUserPageState();
}
class _ListUserPageState extends State<ListUserPage> {
List<Users> userList = [];
List<Users> filteredUserList = [];
List<Map<String, dynamic>> pointsDeVente = [];
bool isLoading = true;
String searchQuery = '';
int? selectedPointDeVenteFilter;
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadData();
_searchController.addListener(_filterUsers);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
Future<void> _loadData() async {
setState(() {
isLoading = true;
});
try {
// Charger les utilisateurs et points de vente en parallèle
final futures = await Future.wait([
AppDatabase.instance.getAllUsers(),
AppDatabase.instance.getPointsDeVente(),
]);
final users = futures[0] as List<Users>;
final points = futures[1] as List<Map<String, dynamic>>;
setState(() {
userList = users;
filteredUserList = users;
pointsDeVente = points;
isLoading = false;
});
} catch (e) {
print('Erreur lors du chargement: $e');
setState(() {
isLoading = false;
});
_showErrorSnackbar('Erreur lors du chargement des données');
}
}
void _filterUsers() {
final query = _searchController.text.toLowerCase();
setState(() {
searchQuery = query;
filteredUserList = userList.where((user) {
final matchesSearch = query.isEmpty ||
user.name.toLowerCase().contains(query) ||
user.lastName.toLowerCase().contains(query) ||
user.username.toLowerCase().contains(query) ||
user.email.toLowerCase().contains(query) ||
(user.roleName?.toLowerCase().contains(query) ?? false);
final matchesPointDeVente = selectedPointDeVenteFilter == null ||
user.pointDeVenteId == selectedPointDeVenteFilter;
return matchesSearch && matchesPointDeVente;
}).toList();
});
}
void _onPointDeVenteFilterChanged(int? pointDeVenteId) {
setState(() {
selectedPointDeVenteFilter = pointDeVenteId;
});
_filterUsers();
}
String _getPointDeVenteName(int? pointDeVenteId) {
if (pointDeVenteId == null) return 'Aucun';
try {
final point = pointsDeVente.firstWhere((p) => p['id'] == pointDeVenteId);
return point['nom'] ?? 'Inconnu';
} catch (e) {
return 'Inconnu';
}
}
Future<void> _deleteUser(Users user, int index) async {
// Vérifier si l'utilisateur peut être supprimé
final canDelete = await _checkCanDeleteUser(user);
if (!canDelete['canDelete']) {
_showCannotDeleteDialog(user, canDelete['reason']);
return;
}
// Afficher la confirmation de suppression
final confirmed = await _showDeleteConfirmation(user);
if (!confirmed) return;
try {
await AppDatabase.instance.deleteUser(user.id!);
setState(() {
userList.removeWhere((u) => u.id == user.id);
filteredUserList.removeWhere((u) => u.id == user.id);
});
_showSuccessSnackbar('Utilisateur supprimé avec succès');
} catch (e) {
print('Erreur lors de la suppression: $e');
_showErrorSnackbar('Erreur lors de la suppression de l\'utilisateur');
}
}
Future<Map<String, dynamic>> _checkCanDeleteUser(Users user) async {
// Ici vous pouvez ajouter des vérifications métier
// Par exemple, vérifier si l'utilisateur a des commandes en cours, etc.
// Pour l'instant, on autorise la suppression sauf pour le Super Admin
if (user.roleName?.toLowerCase() == 'super admin') {
return {
'canDelete': false,
'reason': 'Le Super Admin ne peut pas être supprimé pour des raisons de sécurité.'
};
}
// Vérifier s'il y a des commandes associées à cet utilisateur
try {
final db = AppDatabase.instance;
final commandes = await db.database.then((connection) =>
connection.query('SELECT COUNT(*) as count FROM commandes WHERE commandeurId = ? OR validateurId = ?',
[user.id, user.id])
);
final commandeCount = commandes.first['count'] as int;
if (commandeCount > 0) {
return {
'canDelete': false,
'reason': 'Cet utilisateur a $commandeCount commande(s) associée(s). Impossible de le supprimer.'
};
}
} catch (e) {
print('Erreur lors de la vérification des contraintes: $e');
}
return {'canDelete': true, 'reason': ''};
}
Future<bool> _showDeleteConfirmation(Users user) async {
return await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: const [
Icon(Icons.warning, color: Colors.orange),
SizedBox(width: 8),
Text("Confirmer la suppression"),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Êtes-vous sûr de vouloir supprimer cet utilisateur ?"),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Nom: ${user.name} ${user.lastName}",
style: const TextStyle(fontWeight: FontWeight.w500)),
Text("Username: ${user.username}"),
Text("Email: ${user.email}"),
Text("Rôle: ${user.roleName ?? 'N/A'}"),
Text("Point de vente: ${_getPointDeVenteName(user.pointDeVenteId)}"),
],
),
),
const SizedBox(height: 12),
const Text(
"Cette action est irréversible.",
style: TextStyle(color: Colors.red, fontStyle: FontStyle.italic),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Annuler"),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text("Supprimer"),
),
],
),
) ?? false;
}
void _showCannotDeleteDialog(Users user, String reason) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: const [
Icon(Icons.block, color: Colors.red),
SizedBox(width: 8),
Text("Suppression impossible"),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"L'utilisateur ${user.name} ${user.lastName} ne peut pas être supprimé.",
style: const TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red.shade200),
),
child: Row(
children: [
const Icon(Icons.info, color: Colors.red, size: 20),
const SizedBox(width: 8),
Expanded(child: Text(reason)),
],
),
),
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
child: const Text("Compris"),
),
],
),
);
}
void _showUserDetails(Users user) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Détails de ${user.name} ${user.lastName}"),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailRow("ID", "${user.id}"),
_buildDetailRow("Prénom", user.name),
_buildDetailRow("Nom", user.lastName),
_buildDetailRow("Username", user.username),
_buildDetailRow("Email", user.email),
_buildDetailRow("Rôle", user.roleName ?? 'N/A'),
_buildDetailRow("Point de vente", _getPointDeVenteName(user.pointDeVenteId)),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("Fermer"),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
Get.to(() => EditUserPage(user: user))?.then((_) => _loadData());
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
child: const Text("Modifier"),
),
],
),
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
"$label:",
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Expanded(child: Text(value)),
],
),
);
}
void _showSuccessSnackbar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
Text(message),
],
),
backgroundColor: Colors.green,
duration: const Duration(seconds: 3),
),
);
}
void _showErrorSnackbar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
Text(message),
],
),
backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(title: 'Liste des utilisateurs'),
body: isLoading
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Chargement des utilisateurs...'),
],
),
)
: Column(
children: [
// Barre de recherche et filtres
Container(
padding: const EdgeInsets.all(16),
color: Colors.grey.shade50,
child: Column(
children: [
// Barre de recherche
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Rechercher par nom, username, email...',
prefixIcon: const Icon(Icons.search),
suffixIcon: searchQuery.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.white,
),
),
const SizedBox(height: 12),
// Filtre par point de vente
Row(
children: [
const Icon(Icons.filter_list, color: Colors.grey),
const SizedBox(width: 8),
const Text('Point de vente:'),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonFormField<int?>(
value: selectedPointDeVenteFilter,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
),
filled: true,
fillColor: Colors.white,
),
items: [
const DropdownMenuItem<int?>(
value: null,
child: Text('Tous'),
),
...pointsDeVente.map((point) => DropdownMenuItem<int?>(
value: point['id'] as int,
child: Text(point['nom'] ?? 'N/A'),
)),
],
onChanged: _onPointDeVenteFilterChanged,
),
),
],
),
],
),
),
// Statistiques
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: Colors.blue.shade50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total: ${userList.length} utilisateur(s)',
style: const TextStyle(fontWeight: FontWeight.w500),
),
if (filteredUserList.length != userList.length)
Text(
'Affichés: ${filteredUserList.length}',
style: TextStyle(
color: Colors.blue.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
// Liste des utilisateurs
Expanded(
child: filteredUserList.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
searchQuery.isNotEmpty ? Icons.search_off : Icons.people_outline,
size: 64,
color: Colors.grey.shade400,
),
const SizedBox(height: 16),
Text(
searchQuery.isNotEmpty
? 'Aucun utilisateur trouvé'
: 'Aucun utilisateur dans la base de données',
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade600,
),
),
],
),
)
: RefreshIndicator(
onRefresh: _loadData,
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: filteredUserList.length,
itemBuilder: (context, index) {
Users user = filteredUserList[index];
return _buildUserCard(user, index);
},
),
),
),
],
),
);
}
Widget _buildUserCard(Users user, int index) {
final pointDeVenteName = _getPointDeVenteName(user.pointDeVenteId);
final isSuperAdmin = user.roleName?.toLowerCase() == 'super admin';
return Card(
elevation: 2,
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: isSuperAdmin
? BorderSide(color: Colors.orange.shade300, width: 1)
: BorderSide.none,
),
child: InkWell(
onTap: () => _showUserDetails(user),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête avec nom et badge
Row(
children: [
CircleAvatar(
backgroundColor: isSuperAdmin ? Colors.orange.shade100 : Colors.blue.shade100,
child: Icon(
isSuperAdmin ? Icons.admin_panel_settings : Icons.person,
color: isSuperAdmin ? Colors.orange.shade700 : Colors.blue.shade700,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${user.name} ${user.lastName}",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
"@${user.username}",
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
),
],
),
),
if (isSuperAdmin)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange.shade300),
),
child: Text(
'ADMIN',
style: TextStyle(
color: Colors.orange.shade800,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 12),
// Informations détaillées
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoChip(Icons.email, user.email),
const SizedBox(height: 4),
_buildInfoChip(Icons.badge, user.roleName ?? 'N/A'),
const SizedBox(height: 4),
_buildInfoChip(Icons.store, pointDeVenteName),
],
),
),
// Boutons d'actions
Column(
children: [
IconButton(
icon: const Icon(Icons.visibility, size: 20),
color: Colors.blue.shade600,
onPressed: () => _showUserDetails(user),
tooltip: 'Voir les détails',
),
IconButton(
icon: const Icon(Icons.edit, size: 20),
color: Colors.green.shade600,
onPressed: () {
Get.to(() => EditUserPage(user: user))?.then((_) => _loadData());
},
tooltip: 'Modifier',
),
IconButton(
icon: Icon(
isSuperAdmin ? Icons.lock : Icons.delete,
size: 20,
),
color: isSuperAdmin ? Colors.grey : Colors.red.shade600,
onPressed: isSuperAdmin
? null
: () => _deleteUser(user, index),
tooltip: isSuperAdmin ? 'Protection Super Admin' : 'Supprimer',
),
],
),
],
),
],
),
),
),
);
}
Widget _buildInfoChip(IconData icon, String text) {
return Row(
children: [
Icon(icon, size: 14, color: Colors.grey.shade600),
const SizedBox(width: 6),
Expanded(
child: Text(
text,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
),
overflow: TextOverflow.ellipsis,
),
),
],
);
}
}