807 lines
26 KiB
TypeScript
807 lines
26 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useMemo, useCallback } 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";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
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;
|
|
|
|
// DialogWrapper component to better handle dialog state
|
|
function DialogWrapper({
|
|
open,
|
|
onOpenChange,
|
|
onAfterClose,
|
|
triggerComponent,
|
|
children,
|
|
className
|
|
}: {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onAfterClose?: () => void;
|
|
triggerComponent?: React.ReactNode;
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
const handleOpenChange = useCallback((open: boolean) => {
|
|
onOpenChange(open);
|
|
|
|
if (!open && onAfterClose) {
|
|
// Use a longer timeout to ensure all animations are complete
|
|
setTimeout(onAfterClose, 300);
|
|
}
|
|
}, [onOpenChange, onAfterClose]);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
{triggerComponent && <DialogTrigger asChild>{triggerComponent}</DialogTrigger>}
|
|
<DialogContent className={className}>
|
|
{children}
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
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,
|
|
firstName: formData.firstName,
|
|
lastName: formData.lastName,
|
|
}),
|
|
});
|
|
|
|
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: [],
|
|
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();
|
|
|
|
setManageRolesDialog(false);
|
|
setSelectedUser(null);
|
|
setFormData(prev => ({ ...prev, roles: [] }));
|
|
|
|
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();
|
|
|
|
setFormData({
|
|
username: "",
|
|
lastName: "",
|
|
firstName: "",
|
|
email: "",
|
|
password: "",
|
|
roles: [],
|
|
enabled: true,
|
|
});
|
|
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, email: string) => {
|
|
try {
|
|
const response = await fetch(`/api/users?id=${userId}&email=${encodeURIComponent(email)}`, {
|
|
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 temporaire:");
|
|
if (!newPassword) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/users/${userId}/password`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
password: newPassword,
|
|
temporary: true
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Erreur lors du changement de mot de passe");
|
|
}
|
|
|
|
toast({
|
|
title: "Succès",
|
|
description: "Le mot de passe temporaire a été défini avec succès. L'utilisateur devra le changer à sa première connexion.",
|
|
});
|
|
} 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
|
|
);
|
|
|
|
// Define reset function callbacks
|
|
const resetNewUserForm = useCallback(() => {
|
|
setFormData({
|
|
username: "",
|
|
lastName: "",
|
|
firstName: "",
|
|
email: "",
|
|
password: "",
|
|
roles: [],
|
|
enabled: true,
|
|
});
|
|
}, []);
|
|
|
|
const resetEditForm = useCallback(() => {
|
|
setFormData({
|
|
username: "",
|
|
lastName: "",
|
|
firstName: "",
|
|
email: "",
|
|
password: "",
|
|
roles: [],
|
|
enabled: true,
|
|
});
|
|
setSelectedUser(null);
|
|
}, []);
|
|
|
|
const resetRolesForm = useCallback(() => {
|
|
setFormData(prev => ({ ...prev, roles: [] }));
|
|
setSelectedUser(null);
|
|
}, []);
|
|
|
|
if (!session) return null;
|
|
if (loading) return <div className="text-center p-4">Loading...</div>;
|
|
|
|
return (
|
|
<div className="space-y-4 p-6 bg-gray-100 rounded-lg">
|
|
<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 bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
<DialogWrapper
|
|
open={newUserDialog}
|
|
onOpenChange={setNewUserDialog}
|
|
onAfterClose={resetNewUserForm}
|
|
triggerComponent={
|
|
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
|
Ajouter un utilisateur
|
|
</Button>
|
|
}
|
|
className="max-h-[85vh] overflow-y-auto bg-white text-black border border-gray-300"
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-gray-900">Nouvel Utilisateur</DialogTitle>
|
|
</DialogHeader>
|
|
<form onSubmit={handleAddUser} className="space-y-3">
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="username" className="text-gray-900">Nom d'utilisateur</Label>
|
|
<Input
|
|
id="username"
|
|
value={formData.username}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value.trim() }))}
|
|
required
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email" className="text-gray-900">Email</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value.trim() }))}
|
|
required
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="firstName" className="text-gray-900">Prénom</Label>
|
|
<Input
|
|
id="firstName"
|
|
value={formData.firstName}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value.trim() }))}
|
|
required
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="lastName" className="text-gray-900">Nom</Label>
|
|
<Input
|
|
id="lastName"
|
|
value={formData.lastName}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value.trim() }))}
|
|
required
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password" className="text-gray-900">Mot de passe</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
value={formData.password}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
|
|
required
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label className="text-gray-900">Rôles</Label>
|
|
<div className="grid grid-cols-2 gap-2 max-h-[120px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
|
{roles.map((role) => (
|
|
<div key={role.id} className="flex items-center space-x-2">
|
|
<Checkbox
|
|
id={`role-${role.id}`}
|
|
checked={formData.roles.includes(role.name)}
|
|
onCheckedChange={(checked) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
roles: checked
|
|
? [...prev.roles, role.name]
|
|
: prev.roles.filter(r => r !== role.name)
|
|
}));
|
|
}}
|
|
className="border-gray-500"
|
|
/>
|
|
<Label htmlFor={`role-${role.id}`} className="text-sm text-gray-900">{role.name}</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<Button type="submit" className="w-full bg-blue-600 hover:bg-blue-700 text-white">Créer Utilisateur</Button>
|
|
</form>
|
|
</DialogWrapper>
|
|
</div>
|
|
|
|
<Table className="bg-white border border-gray-300 rounded-md">
|
|
<TableHeader className="bg-gray-50">
|
|
<TableRow>
|
|
<TableHead className="text-gray-900">Username</TableHead>
|
|
<TableHead className="text-gray-900">First Name</TableHead>
|
|
<TableHead className="text-gray-900">Last Name</TableHead>
|
|
<TableHead className="text-gray-900">Email</TableHead>
|
|
<TableHead className="text-gray-900">Created At</TableHead>
|
|
<TableHead className="text-gray-900">Roles</TableHead>
|
|
<TableHead className="text-gray-900 text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{paginatedUsers.map((user) => (
|
|
<TableRow key={user.id} className="hover:bg-gray-50 border-t border-gray-200">
|
|
<TableCell className="text-gray-900">{user.username}</TableCell>
|
|
<TableCell className="text-gray-900">{user.firstName || "-"}</TableCell>
|
|
<TableCell className="text-gray-900">{user.lastName || "-"}</TableCell>
|
|
<TableCell className="text-gray-900">{user.email || "-"}</TableCell>
|
|
<TableCell className="text-gray-900">
|
|
{new Date(user.createdTimestamp).toLocaleDateString()}
|
|
</TableCell>
|
|
<TableCell className="text-gray-900">
|
|
<div className="flex flex-wrap gap-1">
|
|
{(user.roles || []).map((role) => (
|
|
<span key={role} className="inline-flex items-center rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-700/10">
|
|
{role}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-gray-900 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" className="bg-white border border-gray-300">
|
|
<DropdownMenuLabel className="text-gray-900">Actions</DropdownMenuLabel>
|
|
<DropdownMenuItem
|
|
onClick={() => handleEdit(user.id)}
|
|
className="text-gray-900 hover:bg-gray-100"
|
|
>
|
|
<Edit className="mr-2 h-4 w-4" />
|
|
Modifier
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => handleManageRoles(user.id)}
|
|
className="text-gray-900 hover:bg-gray-100"
|
|
>
|
|
<UserPlus className="mr-2 h-4 w-4" />
|
|
Gérer les rôles
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => handleChangePassword(user.id)}
|
|
className="text-gray-900 hover:bg-gray-100"
|
|
>
|
|
<Key className="mr-2 h-4 w-4" />
|
|
Changer le mot de passe
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => handleToggleUserStatus(user.id, user.enabled)}
|
|
className="text-gray-900 hover:bg-gray-100"
|
|
>
|
|
{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 hover:bg-red-50 hover:text-red-700"
|
|
onClick={() => handleDelete(user.id, user.email)}
|
|
>
|
|
<Trash className="mr-2 h-4 w-4" />
|
|
Supprimer
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
<DialogWrapper
|
|
open={editUserDialog}
|
|
onOpenChange={setEditUserDialog}
|
|
onAfterClose={resetEditForm}
|
|
className="bg-white text-black border border-gray-300"
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-gray-900">Modifier l'utilisateur</DialogTitle>
|
|
</DialogHeader>
|
|
<form onSubmit={handleUpdateUser} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-firstName" className="text-gray-900">Prénom</Label>
|
|
<Input
|
|
id="edit-firstName"
|
|
value={formData.firstName}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, firstName: e.target.value }))}
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
autoFocus
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-lastName" className="text-gray-900">Nom</Label>
|
|
<Input
|
|
id="edit-lastName"
|
|
value={formData.lastName}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, lastName: e.target.value }))}
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="edit-email" className="text-gray-900">Email</Label>
|
|
<Input
|
|
id="edit-email"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
|
className="bg-white text-gray-900 border-gray-300"
|
|
/>
|
|
</div>
|
|
<div className="flex justify-end space-x-2">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setEditUserDialog(false);
|
|
setSelectedUser(null);
|
|
setFormData({
|
|
username: "",
|
|
lastName: "",
|
|
firstName: "",
|
|
email: "",
|
|
password: "",
|
|
roles: [],
|
|
enabled: true,
|
|
});
|
|
}}
|
|
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
|
>
|
|
Annuler
|
|
</Button>
|
|
<Button type="submit" className="bg-blue-600 hover:bg-blue-700 text-white">
|
|
Modifier
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogWrapper>
|
|
|
|
<DialogWrapper
|
|
open={manageRolesDialog}
|
|
onOpenChange={setManageRolesDialog}
|
|
onAfterClose={resetRolesForm}
|
|
className="bg-white text-black border border-gray-300"
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-gray-900">Gérer les rôles pour {selectedUser?.username}</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label className="text-gray-900">Rôles disponibles</Label>
|
|
<div className="grid grid-cols-2 gap-2 max-h-[200px] overflow-y-auto border border-gray-300 rounded-md p-2 bg-white">
|
|
{roles.map((role) => (
|
|
<div key={role.id} className="flex items-center space-x-2 p-2 rounded-md hover:bg-gray-100">
|
|
<Checkbox
|
|
id={`manage-role-${role.id}`}
|
|
checked={formData.roles.includes(role.name)}
|
|
onCheckedChange={(checked) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
roles: checked
|
|
? [...prev.roles, role.name]
|
|
: prev.roles.filter(r => r !== role.name)
|
|
}));
|
|
}}
|
|
className="border-gray-500"
|
|
/>
|
|
<Label
|
|
htmlFor={`manage-role-${role.id}`}
|
|
className="text-sm text-gray-900"
|
|
>
|
|
{role.name}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex justify-end space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => {
|
|
setManageRolesDialog(false);
|
|
setSelectedUser(null);
|
|
setFormData(prev => ({ ...prev, roles: [] }));
|
|
}}
|
|
className="border-gray-300 text-gray-800 hover:bg-gray-100"
|
|
>
|
|
Annuler
|
|
</Button>
|
|
<Button onClick={handleUpdateRoles} className="bg-blue-600 hover:bg-blue-700 text-white">
|
|
Mettre à jour les rôles
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogWrapper>
|
|
</div>
|
|
);
|
|
}
|