"use client"; import { useState, useEffect, useCallback } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Plus, MoreHorizontal, Trash, Edit, Users } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Label } from "@/components/ui/label"; import { toast } from "@/components/ui/use-toast"; interface Group { id: string; name: string; path: string; membersCount: number; } interface User { id: string; username: string; email: string; lastName: string; firstName: string; } interface ApiError { message: string; } interface GroupsTableProps { userRole?: string[]; } // 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 GroupsTable({ userRole = [] }: GroupsTableProps) { const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [newGroupDialog, setNewGroupDialog] = useState(false); const [newGroupName, setNewGroupName] = useState(""); const [modifyGroupDialog, setModifyGroupDialog] = useState(false); const [selectedGroup, setSelectedGroup] = useState(null); const [modifiedGroupName, setModifiedGroupName] = useState(""); const [manageMembersDialog, setManageMembersDialog] = useState(false); const [groupMembers, setGroupMembers] = useState([]); const [availableUsers, setAvailableUsers] = useState([]); useEffect(() => { fetchGroups(); }, []); const fetchGroups = async () => { try { setLoading(true); const response = await fetch("/api/groups"); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Erreur lors de la récupération des groupes"); } const groupsWithCounts = await Promise.all( (Array.isArray(data) ? data : []).map(async (group) => { try { const membersResponse = await fetch(`/api/groups/${group.id}/members`); if (membersResponse.ok) { const members = await membersResponse.json(); return { ...group, membersCount: Array.isArray(members) ? members.length : 0 }; } return group; } catch (error) { console.error(`Error fetching members for group ${group.id}:`, error); return group; } }) ); setGroups(groupsWithCounts); } catch (error) { toast({ title: "Erreur", description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } finally { setLoading(false); } }; const handleCreateGroup = async () => { try { if (!newGroupName.trim()) { throw new Error("Le nom du groupe est requis"); } const response = await fetch("/api/groups", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: newGroupName }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Erreur lors de la création du groupe"); } setGroups(prev => [...prev, data]); setNewGroupDialog(false); setNewGroupName(""); toast({ title: "Succès", description: "Le groupe a été créé avec succès", }); } catch (error) { toast({ title: "Erreur", description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } }; const handleDeleteGroup = async (groupId: string) => { try { const response = await fetch(`/api/groups/${groupId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Erreur lors de la suppression du groupe"); } setGroups(prev => prev.filter(group => group.id !== groupId)); toast({ title: "Succès", description: "Le groupe a été supprimé avec succès", }); } catch (error) { toast({ title: "Erreur", description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } }; const handleModifyGroup = async (groupId: string) => { try { const group = groups.find(g => g.id === groupId); if (!group) return; setSelectedGroup(group); setModifiedGroupName(group.name); setModifyGroupDialog(true); } catch (error) { toast({ title: "Erreur", description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } }; const handleUpdateGroup = async () => { try { if (!selectedGroup || !modifiedGroupName.trim()) { throw new Error("Le nom du groupe est requis"); } const response = await fetch(`/api/groups/${selectedGroup.id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: modifiedGroupName }), }); if (!response.ok) { throw new Error("Erreur lors de la modification du groupe"); } setGroups(prev => prev.map(group => group.id === selectedGroup.id ? { ...group, name: modifiedGroupName } : group )); setModifyGroupDialog(false); setSelectedGroup(null); setModifiedGroupName(""); toast({ title: "Succès", description: "Le groupe a été modifié avec succès", }); } catch (error) { toast({ title: "Erreur", description: error instanceof Error ? error.message : "Une erreur est survenue", variant: "destructive", }); } }; const handleManageMembers = async (groupId: string) => { const group = groups.find(g => g.id === groupId); if (!group) return; setSelectedGroup(group); try { const membersResponse = await fetch(`/api/groups/${groupId}/members`); if (!membersResponse.ok) throw new Error("Failed to fetch group members"); const members = await membersResponse.json(); setGroupMembers(members); setGroups(prev => prev.map(g => g.id === groupId ? { ...g, membersCount: members.length } : g )); const usersResponse = await fetch("/api/users"); if (!usersResponse.ok) throw new Error("Failed to fetch users"); const users = await usersResponse.json(); setAvailableUsers(users.filter((user: User) => !members.some((m: User) => m.id === user.id))); setManageMembersDialog(true); } catch (error) { toast({ title: "Erreur", description: "Erreur lors de la récupération des membres", variant: "destructive", }); } }; const handleAddMember = async (userId: string) => { if (!selectedGroup) return; try { const response = await fetch(`/api/groups/${selectedGroup.id}/members`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId }), }); if (!response.ok) { throw new Error("Failed to add member"); } const updatedMember = availableUsers.find(u => u.id === userId); if (updatedMember) { setGroupMembers(prev => [...prev, updatedMember]); setAvailableUsers(prev => prev.filter(u => u.id !== userId)); } setGroups(prev => prev.map(group => group.id === selectedGroup.id ? { ...group, membersCount: group.membersCount + 1 } : group )); toast({ title: "Success", description: "Member added successfully", }); } catch (error) { toast({ title: "Error", description: error instanceof Error ? error.message : "An error occurred", variant: "destructive", }); } }; const handleRemoveMember = async (userId: string) => { if (!selectedGroup) return; try { const response = await fetch(`/api/groups/${selectedGroup.id}/members/${userId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Failed to remove member"); } const removedMember = groupMembers.find(u => u.id === userId); if (removedMember) { setGroupMembers(prev => prev.filter(u => u.id !== userId)); setAvailableUsers(prev => [...prev, removedMember]); } setGroups(prev => prev.map(group => group.id === selectedGroup.id ? { ...group, membersCount: Math.max(0, group.membersCount - 1) } : group )); toast({ title: "Success", description: "Member removed successfully", }); } catch (error) { toast({ title: "Error", description: error instanceof Error ? error.message : "An error occurred", variant: "destructive", }); } }; // Reset callbacks const resetNewGroupForm = useCallback(() => { setNewGroupName(""); }, []); const resetModifyGroupForm = useCallback(() => { setSelectedGroup(null); setModifiedGroupName(""); }, []); const resetMembersForm = useCallback(() => { setSelectedGroup(null); setGroupMembers([]); setAvailableUsers([]); }, []); if (loading) return
Loading...
; return (
setSearchTerm(e.target.value)} className="max-w-xs bg-white text-gray-900 border-gray-300" /> Ajouter un groupe } className="bg-white text-black border border-gray-300" > Nouveau Groupe
setNewGroupName(e.target.value)} placeholder="Entrez le nom du groupe" className="bg-white text-gray-900 border-gray-300" />
Nom Chemin Membres Actions {groups .filter(group => group.name.toLowerCase().includes(searchTerm.toLowerCase()) || group.path.toLowerCase().includes(searchTerm.toLowerCase()) ) .map((group) => ( {group.name} {group.path} {group.membersCount} Actions handleModifyGroup(group.id)} className="text-gray-900 hover:bg-gray-100" > Modifier handleManageMembers(group.id)} className="text-gray-900 hover:bg-gray-100" > Gérer les membres handleDeleteGroup(group.id)} className="text-red-600 hover:bg-red-50 hover:text-red-700" > Supprimer ))}
Modifier le groupe
setModifiedGroupName(e.target.value)} className="bg-white text-gray-900 border-gray-300" autoFocus />
Gérer les membres - {selectedGroup?.name}

Membres actuels

{groupMembers.length === 0 ? (

Ce groupe n'a pas de membres.

) : (
{groupMembers.map((member) => (
{member.username}
{member.firstName} {member.lastName} ({member.email})
))}
)}

Ajouter des membres

setSearchTerm(e.target.value)} className="mb-2 bg-white text-gray-900 border-gray-300" />
{availableUsers .filter(user => !groupMembers.some(member => member.id === user.id) && (user.username.toLowerCase().includes(searchTerm.toLowerCase()) || user.email.toLowerCase().includes(searchTerm.toLowerCase()) || user.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || user.lastName.toLowerCase().includes(searchTerm.toLowerCase())) ) .map((user) => (
{user.username}
{user.firstName} {user.lastName} ({user.email})
))}
); }