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