Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Users, Loader2, Crown, Shield, User as UserIcon } from 'lucide-react';
import api from '../../services/api';
import toast from 'react-hot-toast';
const ROLES = [
{ value: 'user', label: 'User', icon: UserIcon, color: 'bg-gray-100 text-gray-800' },
{ value: 'tester', label: 'Tester', icon: Shield, color: 'bg-blue-100 text-blue-800' },
{ value: 'admin', label: 'Admin', icon: Crown, color: 'bg-purple-100 text-purple-800' }
];
export default function UsersManagement() {
const queryClient = useQueryClient();
const [selectedRole, setSelectedRole] = useState(null);
const { data: users, isLoading } = useQuery({
queryKey: ['admin-users'],
queryFn: async () => {
const response = await api.get('/users');
return response.data.data?.users || [];
}
});
const updateRoleMutation = useMutation({
mutationFn: async ({ userId, role }) => {
const response = await api.patch(`/users/${userId}`, { role });
return response.data;
},
onSuccess: () => {
queryClient.invalidateQueries(['admin-users']);
toast.success('Rôle mis à jour');
setSelectedRole(null);
},
onError: (error) => {
toast.error(error.response?.data?.message || 'Erreur lors de la mise à jour');
}
});
const handleRoleChange = (userId, newRole) => {
updateRoleMutation.mutate({ userId, role: newRole });
};
const getRoleIcon = (role) => {
const roleConfig = ROLES.find(r => r.value === role);
const Icon = roleConfig?.icon || UserIcon;
const colorClass = roleConfig?.color || 'bg-gray-100 text-gray-800';
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colorClass}`}>
<Icon className="w-3 h-3 mr-1" />
{role}
</span>
);
};
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<Loader2 className="w-8 h-8 animate-spin text-indigo-600" />
</div>
);
}
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-gray-900">Gestion des Utilisateurs</h2>
<p className="text-gray-600">Gérez les rôles globaux des utilisateurs</p>
</div>
<div className="bg-white rounded-lg shadow overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Utilisateur
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Rôle global
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Dernière connexion
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{users.map((user) => (
<tr key={user._id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-3">
<span className="text-indigo-600 font-medium">
{user.name?.charAt(0).toUpperCase() || user.email?.charAt(0).toUpperCase() || '?'}
</span>
</div>
<div>
<p className="text-sm font-medium text-gray-900">{user.name || 'N/A'}</p>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<p className="text-sm text-gray-500">{user.email}</p>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{selectedRole === user._id ? (
<select
autoFocus
defaultValue={user.role}
onChange={(e) => handleRoleChange(user._id, e.target.value)}
onBlur={() => setSelectedRole(null)}
className="text-sm border border-gray-300 rounded-lg px-2 py-1 focus:ring-2 focus:ring-indigo-500"
>
{ROLES.map(role => (
<option key={role.value} value={role.value}>{role.label}</option>
))}
</select>
) : (
<button
onClick={() => setSelectedRole(user._id)}
className="hover:opacity-75"
>
{getRoleIcon(user.role)}
</button>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<p className="text-sm text-gray-500">
{user.lastLogin ? new Date(user.lastLogin).toLocaleDateString() : 'Jamais'}
</p>
</td>
</tr>
))}
</tbody>
</table>
{users.length === 0 && (
<div className="text-center py-12">
<Users className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500">Aucun utilisateur trouvé</p>
</div>
)}
</div>
</div>
);
}
|