"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 ( {triggerComponent && {triggerComponent}} {children} ); } export function UsersTable({ userRole = [] }: UsersTableProps) { const { data: session, status } = useSession(); const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); 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(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
Loading...
; return (
setSearchTerm(e.target.value)} className="max-w-sm bg-white text-gray-900 border-gray-300" /> Ajouter un utilisateur } className="max-h-[85vh] overflow-y-auto bg-white text-black border border-gray-300" > Nouvel Utilisateur
setFormData(prev => ({ ...prev, username: e.target.value.trim() }))} required className="bg-white text-gray-900 border-gray-300" />
setFormData(prev => ({ ...prev, email: e.target.value.trim() }))} required className="bg-white text-gray-900 border-gray-300" />
setFormData(prev => ({ ...prev, firstName: e.target.value.trim() }))} required className="bg-white text-gray-900 border-gray-300" />
setFormData(prev => ({ ...prev, lastName: e.target.value.trim() }))} required className="bg-white text-gray-900 border-gray-300" />
setFormData(prev => ({ ...prev, password: e.target.value }))} required className="bg-white text-gray-900 border-gray-300" />
{roles.map((role) => (
{ setFormData(prev => ({ ...prev, roles: checked ? [...prev.roles, role.name] : prev.roles.filter(r => r !== role.name) })); }} className="border-gray-500" />
))}
Username First Name Last Name Email Created At Roles Actions {paginatedUsers.map((user) => ( {user.username} {user.firstName || "-"} {user.lastName || "-"} {user.email || "-"} {new Date(user.createdTimestamp).toLocaleDateString()}
{(user.roles || []).map((role) => ( {role} ))}
Actions handleEdit(user.id)} className="text-gray-900 hover:bg-gray-100" > Modifier handleManageRoles(user.id)} className="text-gray-900 hover:bg-gray-100" > Gérer les rôles handleChangePassword(user.id)} className="text-gray-900 hover:bg-gray-100" > Changer le mot de passe handleToggleUserStatus(user.id, user.enabled)} className="text-gray-900 hover:bg-gray-100" > {user.enabled ? ( <> Désactiver ) : ( <> Activer )} handleDelete(user.id, user.email)} > Supprimer
))}
Modifier l'utilisateur
setFormData(prev => ({ ...prev, firstName: e.target.value }))} className="bg-white text-gray-900 border-gray-300" autoFocus />
setFormData(prev => ({ ...prev, lastName: e.target.value }))} className="bg-white text-gray-900 border-gray-300" />
setFormData(prev => ({ ...prev, email: e.target.value }))} className="bg-white text-gray-900 border-gray-300" />
Gérer les rôles pour {selectedUser?.username}
{roles.map((role) => (
{ setFormData(prev => ({ ...prev, roles: checked ? [...prev.roles, role.name] : prev.roles.filter(r => r !== role.name) })); }} className="border-gray-500" />
))}
); }