758 lines
24 KiB
TypeScript
758 lines
24 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect, useMemo } from "react";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table";
|
||
import { Button } from "@/components/ui/button";
|
||
import { useSession } from "next-auth/react";
|
||
import { Input } from "@/components/ui/input";
|
||
import { MoreHorizontal, Trash, Edit, UserPlus, Key, Lock, Unlock } from "lucide-react";
|
||
import {
|
||
DropdownMenu,
|
||
DropdownMenuContent,
|
||
DropdownMenuItem,
|
||
DropdownMenuLabel,
|
||
DropdownMenuSeparator,
|
||
DropdownMenuTrigger,
|
||
} from "@/components/ui/dropdown-menu";
|
||
import { toast } from "@/components/ui/use-toast";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from "@/components/ui/dialog";
|
||
import { Label } from "@/components/ui/label";
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select";
|
||
|
||
interface User {
|
||
id: string;
|
||
username: string;
|
||
firstName: string;
|
||
lastName: string;
|
||
email: string;
|
||
createdTimestamp: number;
|
||
roles: string[];
|
||
enabled: boolean;
|
||
}
|
||
|
||
interface Role {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
composite: boolean;
|
||
clientRole: boolean;
|
||
containerId: string;
|
||
}
|
||
|
||
interface UsersTableProps {
|
||
userRole?: string[];
|
||
}
|
||
|
||
const ITEMS_PER_PAGE = 10;
|
||
|
||
export function UsersTable({ userRole = [] }: UsersTableProps) {
|
||
const { data: session, status } = useSession();
|
||
const [users, setUsers] = useState<User[]>([]);
|
||
const [roles, setRoles] = useState<Role[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const [searchTerm, setSearchTerm] = useState("");
|
||
const [newUserDialog, setNewUserDialog] = useState(false);
|
||
const [editUserDialog, setEditUserDialog] = useState(false);
|
||
const [manageRolesDialog, setManageRolesDialog] = useState(false);
|
||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||
const [formData, setFormData] = useState({
|
||
username: "",
|
||
lastName: "",
|
||
firstName: "",
|
||
email: "",
|
||
password: "",
|
||
roles: [] as string[],
|
||
enabled: true,
|
||
});
|
||
|
||
useEffect(() => {
|
||
fetchUsers();
|
||
fetchRoles();
|
||
}, []);
|
||
|
||
const fetchRoles = async () => {
|
||
try {
|
||
const response = await fetch("/api/roles");
|
||
if (!response.ok) {
|
||
throw new Error("Failed to fetch roles");
|
||
}
|
||
const data = await response.json();
|
||
setRoles(data);
|
||
} catch (error) {
|
||
console.error("Error fetching roles:", error);
|
||
toast({
|
||
title: "Erreur",
|
||
description: "Erreur lors de la récupération des rôles",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const fetchUsers = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const response = await fetch("/api/users");
|
||
const data = await response.json();
|
||
setUsers(data);
|
||
} catch (error) {
|
||
console.error("Error fetching users:", error);
|
||
toast({
|
||
title: "Erreur",
|
||
description: "Erreur lors de la récupération des utilisateurs",
|
||
variant: "destructive",
|
||
});
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleAddUser = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
try {
|
||
const response = await fetch("/api/users", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify(formData),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data.error || "Erreur lors de la création de l'utilisateur");
|
||
}
|
||
|
||
setUsers(prev => [...prev, data.user]);
|
||
setNewUserDialog(false);
|
||
setFormData({
|
||
username: "",
|
||
firstName: "",
|
||
lastName: "",
|
||
email: "",
|
||
password: "",
|
||
roles: [],
|
||
enabled: true,
|
||
});
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: "L'utilisateur a été créé avec succès",
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleEdit = async (userId: string) => {
|
||
const user = users.find(u => u.id === userId);
|
||
if (!user) return;
|
||
|
||
setSelectedUser(user);
|
||
setFormData({
|
||
username: user.username,
|
||
firstName: user.firstName || "",
|
||
lastName: user.lastName || "",
|
||
email: user.email || "",
|
||
password: "",
|
||
roles: user.roles || [],
|
||
enabled: user.enabled,
|
||
});
|
||
setEditUserDialog(true);
|
||
};
|
||
|
||
const handleManageRoles = async (userId: string) => {
|
||
const user = users.find(u => u.id === userId);
|
||
if (!user) return;
|
||
|
||
setSelectedUser(user);
|
||
setFormData(prev => ({
|
||
...prev,
|
||
roles: user.roles || [],
|
||
username: user.username,
|
||
firstName: user.firstName || "",
|
||
lastName: user.lastName || "",
|
||
email: user.email || "",
|
||
}));
|
||
setManageRolesDialog(true);
|
||
};
|
||
|
||
const handleUpdateRoles = async () => {
|
||
if (!selectedUser) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/users/${selectedUser.id}/roles`, {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
roles: formData.roles,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Erreur lors de la mise à jour des rôles");
|
||
}
|
||
|
||
await fetchUsers();
|
||
|
||
setFormData({
|
||
username: "",
|
||
lastName: "",
|
||
firstName: "",
|
||
email: "",
|
||
password: "",
|
||
roles: [],
|
||
enabled: true,
|
||
});
|
||
setManageRolesDialog(false);
|
||
setSelectedUser(null);
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: "Les rôles ont été mis à jour avec succès",
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleUpdateUser = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!selectedUser) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/users/${selectedUser.id}`, {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
firstName: formData.firstName,
|
||
lastName: formData.lastName,
|
||
email: formData.email,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Erreur lors de la modification de l'utilisateur");
|
||
}
|
||
|
||
await fetchUsers();
|
||
|
||
setEditUserDialog(false);
|
||
setSelectedUser(null);
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: "L'utilisateur a été modifié avec succès",
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (userId: string) => {
|
||
try {
|
||
const response = await fetch(`/api/users/${userId}`, {
|
||
method: "DELETE",
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (!response.ok) {
|
||
throw new Error(data.error || "Erreur lors de la suppression de l'utilisateur");
|
||
}
|
||
|
||
setUsers(prevUsers => prevUsers.filter(user => user.id !== userId));
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: "L'utilisateur a été supprimé avec succès",
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleChangePassword = async (userId: string) => {
|
||
const newPassword = prompt("Entrez le nouveau mot de passe:");
|
||
if (!newPassword) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/users/${userId}/password`, {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ password: newPassword }),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Erreur lors du changement de mot de passe");
|
||
}
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: "Le mot de passe a été modifié avec succès",
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleToggleUserStatus = async (userId: string, currentStatus: boolean) => {
|
||
try {
|
||
const response = await fetch(`/api/users/${userId}`, {
|
||
method: "PUT",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ enabled: !currentStatus }),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error("Erreur lors de la modification du statut de l'utilisateur");
|
||
}
|
||
|
||
setUsers(prevUsers => prevUsers.map(u =>
|
||
u.id === userId ? { ...u, enabled: !currentStatus } : u
|
||
));
|
||
|
||
toast({
|
||
title: "Succès",
|
||
description: `L'utilisateur a été ${!currentStatus ? "activé" : "désactivé"} avec succès`,
|
||
});
|
||
} catch (error) {
|
||
toast({
|
||
title: "Erreur",
|
||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
||
variant: "destructive",
|
||
});
|
||
}
|
||
};
|
||
|
||
const filteredUsers = useMemo(() => {
|
||
let filtered = users;
|
||
|
||
if (searchTerm) {
|
||
filtered = filtered.filter(user =>
|
||
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
user.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
user.firstName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
user.lastName?.toLowerCase().includes(searchTerm.toLowerCase())
|
||
);
|
||
}
|
||
|
||
return filtered;
|
||
}, [users, searchTerm]);
|
||
|
||
const totalPages = Math.ceil(filteredUsers.length / ITEMS_PER_PAGE);
|
||
const paginatedUsers = filteredUsers.slice(
|
||
(currentPage - 1) * ITEMS_PER_PAGE,
|
||
currentPage * ITEMS_PER_PAGE
|
||
);
|
||
|
||
if (!session) return null;
|
||
if (loading) return <div className="text-center p-4">Loading...</div>;
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="flex justify-between items-center">
|
||
<Input
|
||
type="text"
|
||
placeholder="Rechercher un utilisateur..."
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
className="max-w-sm"
|
||
/>
|
||
<Dialog open={newUserDialog} onOpenChange={setNewUserDialog}>
|
||
<DialogTrigger asChild>
|
||
<Button>Ajouter un utilisateur</Button>
|
||
</DialogTrigger>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Nouvel utilisateur</DialogTitle>
|
||
</DialogHeader>
|
||
<form onSubmit={handleAddUser} className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="username">Nom d'utilisateur</Label>
|
||
<Input
|
||
id="username"
|
||
value={formData.username}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value }))}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="lastName">Nom</Label>
|
||
<Input
|
||
id="lastName"
|
||
value={formData.lastName}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="firstName">Prénom</Label>
|
||
<Input
|
||
id="firstName"
|
||
value={formData.firstName}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="email">Email</Label>
|
||
<Input
|
||
id="email"
|
||
type="email"
|
||
value={formData.email}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="password">Mot de passe</Label>
|
||
<Input
|
||
id="password"
|
||
type="password"
|
||
value={formData.password}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="roles">Rôles</Label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{roles.map((role) => (
|
||
<div key={role.id} className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
id={`role-${role.id}`}
|
||
checked={formData.roles.includes(role.name)}
|
||
onChange={(e) => {
|
||
setFormData(prev => ({
|
||
...prev,
|
||
roles: e.target.checked
|
||
? [...prev.roles, role.name]
|
||
: prev.roles.filter(r => r !== role.name)
|
||
}));
|
||
}}
|
||
/>
|
||
<label htmlFor={`role-${role.id}`}>{role.name}</label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2 mt-2">
|
||
{formData.roles.map(role => (
|
||
<span key={role} className="px-2 py-1 bg-gray-100 rounded-md text-sm">
|
||
{role}
|
||
<button
|
||
type="button"
|
||
onClick={() => setFormData(prev => ({
|
||
...prev,
|
||
roles: prev.roles.filter(r => r !== role)
|
||
}))}
|
||
className="ml-2 text-gray-500 hover:text-gray-700"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<Button type="submit" className="w-full">
|
||
Créer l'utilisateur
|
||
</Button>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>Nom d'utilisateur</TableHead>
|
||
<TableHead>Prénom</TableHead>
|
||
<TableHead>Nom</TableHead>
|
||
<TableHead>Email</TableHead>
|
||
<TableHead>Date d'inscription</TableHead>
|
||
<TableHead>Rôles</TableHead>
|
||
<TableHead className="text-right">Actions</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{paginatedUsers.map((user) => (
|
||
<TableRow key={user.id}>
|
||
<TableCell>{user.username}</TableCell>
|
||
<TableCell>{user.firstName || "-"}</TableCell>
|
||
<TableCell>{user.lastName || "-"}</TableCell>
|
||
<TableCell>{user.email || "-"}</TableCell>
|
||
<TableCell>
|
||
{new Date(user.createdTimestamp).toLocaleDateString()}
|
||
</TableCell>
|
||
<TableCell>{(user.roles || []).join(", ") || "-"}</TableCell>
|
||
<TableCell className="text-right">
|
||
<DropdownMenu>
|
||
<DropdownMenuTrigger asChild>
|
||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||
<span className="sr-only">Open menu</span>
|
||
<MoreHorizontal className="h-4 w-4" />
|
||
</Button>
|
||
</DropdownMenuTrigger>
|
||
<DropdownMenuContent align="end">
|
||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||
<DropdownMenuItem onClick={() => handleEdit(user.id)}>
|
||
<Edit className="mr-2 h-4 w-4" />
|
||
Modifier
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => handleManageRoles(user.id)}>
|
||
<UserPlus className="mr-2 h-4 w-4" />
|
||
Gérer les rôles
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => handleChangePassword(user.id)}>
|
||
<Key className="mr-2 h-4 w-4" />
|
||
Changer le mot de passe
|
||
</DropdownMenuItem>
|
||
<DropdownMenuItem onClick={() => handleToggleUserStatus(user.id, user.enabled)}>
|
||
{user.enabled ? (
|
||
<>
|
||
<Lock className="mr-2 h-4 w-4" />
|
||
Désactiver
|
||
</>
|
||
) : (
|
||
<>
|
||
<Unlock className="mr-2 h-4 w-4" />
|
||
Activer
|
||
</>
|
||
)}
|
||
</DropdownMenuItem>
|
||
<DropdownMenuSeparator />
|
||
<DropdownMenuItem
|
||
className="text-red-600"
|
||
onClick={() => handleDelete(user.id)}
|
||
>
|
||
<Trash className="mr-2 h-4 w-4" />
|
||
Supprimer
|
||
</DropdownMenuItem>
|
||
</DropdownMenuContent>
|
||
</DropdownMenu>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
<Dialog open={editUserDialog} onOpenChange={setEditUserDialog}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Modifier l'utilisateur</DialogTitle>
|
||
</DialogHeader>
|
||
<form onSubmit={handleUpdateUser} className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="edit-lastName">Nom</Label>
|
||
<Input
|
||
id="edit-lastName"
|
||
value={formData.lastName}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="edit-firstName">Prénom</Label>
|
||
<Input
|
||
id="edit-firstName"
|
||
value={formData.firstName}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="edit-email">Email</Label>
|
||
<Input
|
||
id="edit-email"
|
||
type="email"
|
||
value={formData.email}
|
||
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="edit-roles">Rôles</Label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{roles.map((role) => (
|
||
<div key={role.id} className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
id={`edit-role-${role.id}`}
|
||
checked={formData.roles.includes(role.name)}
|
||
onChange={(e) => {
|
||
setFormData(prev => ({
|
||
...prev,
|
||
roles: e.target.checked
|
||
? [...prev.roles, role.name]
|
||
: prev.roles.filter(r => r !== role.name)
|
||
}));
|
||
}}
|
||
/>
|
||
<label htmlFor={`edit-role-${role.id}`}>{role.name}</label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2 mt-2">
|
||
{formData.roles.map(role => (
|
||
<span key={role} className="px-2 py-1 bg-gray-100 rounded-md text-sm">
|
||
{role}
|
||
<button
|
||
type="button"
|
||
onClick={() => setFormData(prev => ({
|
||
...prev,
|
||
roles: prev.roles.filter(r => r !== role)
|
||
}))}
|
||
className="ml-2 text-gray-500 hover:text-gray-700"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="flex space-x-2">
|
||
<Button type="submit" className="flex-1">
|
||
Modifier
|
||
</Button>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
onClick={handleUpdateRoles}
|
||
className="flex-1"
|
||
>
|
||
Mettre à jour les rôles
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog open={manageRolesDialog} onOpenChange={setManageRolesDialog}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>Gérer les rôles de {selectedUser?.username}</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label>Rôles</Label>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
{roles.map((role) => (
|
||
<div key={role.id} className="flex items-center space-x-2 p-2 rounded-md hover:bg-gray-100">
|
||
<input
|
||
type="checkbox"
|
||
id={`manage-role-${role.id}`}
|
||
checked={formData.roles.includes(role.name)}
|
||
onChange={(e) => {
|
||
setFormData(prev => ({
|
||
...prev,
|
||
roles: e.target.checked
|
||
? [...prev.roles, role.name]
|
||
: prev.roles.filter(r => r !== role.name)
|
||
}));
|
||
}}
|
||
className="h-4 w-4 text-primary focus:ring-primary"
|
||
/>
|
||
<label
|
||
htmlFor={`manage-role-${role.id}`}
|
||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||
>
|
||
{role.name}
|
||
</label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2 mt-4">
|
||
{formData.roles.map(role => (
|
||
<span
|
||
key={role}
|
||
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-primary-foreground"
|
||
>
|
||
{role}
|
||
<button
|
||
type="button"
|
||
onClick={() => setFormData(prev => ({
|
||
...prev,
|
||
roles: prev.roles.filter(r => r !== role)
|
||
}))}
|
||
className="ml-1.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full text-primary-foreground hover:bg-primary-foreground/20 focus:outline-none focus:ring-2 focus:ring-primary-foreground focus:ring-offset-2"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="flex justify-end space-x-2">
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => {
|
||
setManageRolesDialog(false);
|
||
setSelectedUser(null);
|
||
setFormData({
|
||
username: "",
|
||
lastName: "",
|
||
firstName: "",
|
||
email: "",
|
||
password: "",
|
||
roles: [],
|
||
enabled: true,
|
||
});
|
||
}}
|
||
>
|
||
Annuler
|
||
</Button>
|
||
<Button onClick={handleUpdateRoles}>
|
||
Mettre à jour les rôles
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
}
|