NeahFront3/components/groups/groups-table.tsx

501 lines
16 KiB
TypeScript

"use client";
import { useState, useEffect } 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;
}
interface ApiError {
message: string;
}
interface GroupsTableProps {
userRole?: string[];
}
export function GroupsTable({ userRole = [] }: GroupsTableProps) {
const [groups, setGroups] = useState<Group[]>([]);
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<Group | null>(null);
const [modifiedGroupName, setModifiedGroupName] = useState("");
const [manageMembersDialog, setManageMembersDialog] = useState(false);
const [groupMembers, setGroupMembers] = useState<User[]>([]);
const [availableUsers, setAvailableUsers] = useState<User[]>([]);
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");
}
setGroups(Array.isArray(data) ? data : []);
} 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 {
// Fetch group members
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);
// Fetch all users
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 user = availableUsers.find(u => u.id === userId);
if (user) {
setGroupMembers(prev => [...prev, user]);
setAvailableUsers(prev => prev.filter(u => u.id !== userId));
}
toast({
title: "Succès",
description: "Membre ajouté avec succès",
});
} catch (error) {
toast({
title: "Erreur",
description: "Erreur lors de l'ajout du membre",
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 user = groupMembers.find(u => u.id === userId);
if (user) {
setAvailableUsers(prev => [...prev, user]);
setGroupMembers(prev => prev.filter(u => u.id !== userId));
}
toast({
title: "Succès",
description: "Membre retiré avec succès",
});
} catch (error) {
toast({
title: "Erreur",
description: "Erreur lors du retrait du membre",
variant: "destructive",
});
}
};
const filteredGroups = groups.filter(group =>
group.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className='flex flex-col space-y-4'>
<div className='flex justify-between items-center mb-6'>
<h1 className='text-2xl font-semibold text-white'>Gestion des groupes</h1>
<div className="flex items-center space-x-4">
<div className="relative w-64">
<Input
type="text"
placeholder="Rechercher un groupe..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full bg-black/20 border-0 text-white placeholder:text-gray-400"
/>
</div>
<Dialog open={newGroupDialog} onOpenChange={setNewGroupDialog}>
<DialogTrigger asChild>
<Button variant="outline" className="bg-blue-600 text-white hover:bg-blue-700 border-0">
<Plus className="mr-2 h-4 w-4" />
Nouveau groupe
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Créer un nouveau groupe</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Nom du groupe</Label>
<Input
id="name"
value={newGroupName}
onChange={(e) => setNewGroupName(e.target.value)}
placeholder="Entrez le nom du groupe"
/>
</div>
<Button onClick={handleCreateGroup} className="w-full">
Créer le groupe
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</div>
<div className="rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-black/20 border-0">
<TableHead className="text-gray-400">Nom du groupe</TableHead>
<TableHead className="text-gray-400">Chemin</TableHead>
<TableHead className="text-gray-400">Nombre de membres</TableHead>
<TableHead className="text-gray-400 text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredGroups.map((group) => (
<TableRow
key={group.id}
className="border-0 bg-black/10 hover:bg-black/20"
>
<TableCell className="text-white">{group.name}</TableCell>
<TableCell className="text-white">{group.path}</TableCell>
<TableCell className="text-white">{group.membersCount}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0 text-white hover:bg-black/20">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-black/90 border-gray-700">
<DropdownMenuLabel className="text-gray-400">Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => handleModifyGroup(group.id)}
className="text-white hover:bg-black/50"
>
<Edit className="mr-2 h-4 w-4" />
Modifier
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleManageMembers(group.id)}
className="text-white hover:bg-black/50"
>
<Users className="mr-2 h-4 w-4" />
Gérer les membres
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-gray-700" />
<DropdownMenuItem
className="text-red-400 hover:bg-black/50"
onClick={() => handleDeleteGroup(group.id)}
>
<Trash className="mr-2 h-4 w-4" />
Supprimer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<Dialog open={modifyGroupDialog} onOpenChange={setModifyGroupDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Modifier le groupe</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Nom du groupe</Label>
<Input
id="name"
value={modifiedGroupName}
onChange={(e) => setModifiedGroupName(e.target.value)}
placeholder="Entrez le nouveau nom du groupe"
/>
</div>
<Button
onClick={handleUpdateGroup}
className="w-full"
>
Modifier
</Button>
</div>
</DialogContent>
</Dialog>
<Dialog open={manageMembersDialog} onOpenChange={(open) => {
if (!open) {
setSelectedGroup(null);
setGroupMembers([]);
setAvailableUsers([]);
}
setManageMembersDialog(open);
}}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Gérer les membres du groupe {selectedGroup?.name}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>Membres actuels</Label>
<div className="border rounded-md p-4 space-y-2">
{groupMembers.length === 0 ? (
<p className="text-sm text-gray-500">Aucun membre</p>
) : (
groupMembers.map(member => (
<div key={member.id} className="flex items-center justify-between p-2 hover:bg-gray-50 rounded-md">
<div>
<p className="font-medium">{member.username}</p>
<p className="text-sm text-gray-500">{member.email}</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveMember(member.id)}
>
Retirer
</Button>
</div>
))
)}
</div>
</div>
<div className="space-y-2">
<Label>Ajouter des membres</Label>
<div className="border rounded-md p-4 space-y-2">
{availableUsers.length === 0 ? (
<p className="text-sm text-gray-500">Aucun utilisateur disponible</p>
) : (
availableUsers.map(user => (
<div key={user.id} className="flex items-center justify-between p-2 hover:bg-gray-50 rounded-md">
<div>
<p className="font-medium">{user.username}</p>
<p className="text-sm text-gray-500">{user.email}</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleAddMember(user.id)}
>
Ajouter
</Button>
</div>
))
)}
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
);
}