Groups ui team ui
This commit is contained in:
parent
52c720d520
commit
f931da66ca
@ -69,6 +69,10 @@ export default function EquipePage() {
|
|||||||
password: "",
|
password: "",
|
||||||
roles: [] as string[],
|
roles: [] as string[],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// New group dialog state
|
||||||
|
const [newGroupDialogOpen, setNewGroupDialogOpen] = useState(false);
|
||||||
|
const [newGroupName, setNewGroupName] = useState("");
|
||||||
|
|
||||||
// Fetch data on mount
|
// Fetch data on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -473,6 +477,43 @@ export default function EquipePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createGroup = async () => {
|
||||||
|
if (!newGroupName.trim()) {
|
||||||
|
toast({ title: "Erreur", description: "Le nom du groupe est requis", variant: "destructive" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActionLoading("new-group");
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/groups", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ name: newGroupName.trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.message || "Failed to create group");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGroup = await response.json();
|
||||||
|
setGroups(prev => [...prev, newGroup]);
|
||||||
|
setNewGroupDialogOpen(false);
|
||||||
|
setNewGroupName("");
|
||||||
|
toast({ title: "Succès", description: "Groupe créé" });
|
||||||
|
fetchData(); // Refresh data
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating group:", error);
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: error instanceof Error ? error.message : "Échec de la création",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setActionLoading(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-full bg-white">
|
<div className="flex flex-col h-full w-full bg-white">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -595,6 +636,58 @@ export default function EquipePage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
{activeTab === "groups" && (
|
||||||
|
<Dialog open={newGroupDialogOpen} onOpenChange={setNewGroupDialogOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button size="sm" className="h-9 bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
Créer un groupe
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[400px] bg-white">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-gray-900">Nouveau groupe</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4 py-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="groupName" className="text-gray-900">Nom du groupe *</Label>
|
||||||
|
<Input
|
||||||
|
id="groupName"
|
||||||
|
value={newGroupName}
|
||||||
|
onChange={(e) => setNewGroupName(e.target.value)}
|
||||||
|
className="mt-1 bg-white text-gray-900 border-gray-300"
|
||||||
|
placeholder="Ex: Développeurs"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 pt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setNewGroupDialogOpen(false);
|
||||||
|
setNewGroupName("");
|
||||||
|
}}
|
||||||
|
className="border-gray-300 text-gray-700 hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Annuler
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={createGroup}
|
||||||
|
disabled={actionLoading === "new-group" || !newGroupName.trim()}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||||
|
>
|
||||||
|
{actionLoading === "new-group" ? (
|
||||||
|
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||||||
|
) : (
|
||||||
|
<Check className="h-4 w-4 mr-2" />
|
||||||
|
)}
|
||||||
|
Créer
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
|
||||||
<Input
|
<Input
|
||||||
@ -1023,16 +1116,16 @@ export default function EquipePage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Current Members */}
|
{/* Current Members */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-600 mb-2 block">
|
<label className="text-xs font-medium text-gray-900 mb-2 block">
|
||||||
Membres actuels ({groupMembers.length})
|
Membres actuels ({groupMembers.length})
|
||||||
</label>
|
</label>
|
||||||
<div className="max-h-40 overflow-y-auto space-y-1">
|
<div className="max-h-40 overflow-y-auto space-y-1 border rounded-md p-2 bg-white">
|
||||||
{groupMembers.length === 0 ? (
|
{groupMembers.length === 0 ? (
|
||||||
<p className="text-xs text-gray-400 py-2">Aucun membre</p>
|
<p className="text-xs text-gray-500 py-2 text-center">Aucun membre</p>
|
||||||
) : (
|
) : (
|
||||||
groupMembers.map(member => (
|
groupMembers.map(member => (
|
||||||
<div key={member.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
<div key={member.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md hover:bg-gray-100 transition-colors">
|
||||||
<span className="text-sm text-gray-700 truncate">
|
<span className="text-sm text-gray-900 font-medium truncate flex-1">
|
||||||
{member.firstName} {member.lastName}
|
{member.firstName} {member.lastName}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@ -1040,12 +1133,13 @@ export default function EquipePage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => removeMemberFromGroup(member.id)}
|
onClick={() => removeMemberFromGroup(member.id)}
|
||||||
disabled={actionLoading === member.id}
|
disabled={actionLoading === member.id}
|
||||||
className="h-6 w-6 p-0 hover:text-red-600"
|
className="h-7 w-7 p-0 hover:bg-red-50 hover:text-red-600 text-gray-500"
|
||||||
|
title="Retirer du groupe"
|
||||||
>
|
>
|
||||||
{actionLoading === member.id ? (
|
{actionLoading === member.id ? (
|
||||||
<Loader2 className="h-3 w-3 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<X className="h-3 w-3" />
|
<X className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -1056,16 +1150,16 @@ export default function EquipePage() {
|
|||||||
|
|
||||||
{/* Available Users */}
|
{/* Available Users */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-600 mb-2 block">
|
<label className="text-xs font-medium text-gray-900 mb-2 block">
|
||||||
Ajouter des membres
|
Ajouter des membres
|
||||||
</label>
|
</label>
|
||||||
<div className="max-h-40 overflow-y-auto space-y-1">
|
<div className="max-h-40 overflow-y-auto space-y-1 border rounded-md p-2 bg-white">
|
||||||
{availableUsers.length === 0 ? (
|
{availableUsers.length === 0 ? (
|
||||||
<p className="text-xs text-gray-400 py-2">Tous les utilisateurs sont membres</p>
|
<p className="text-xs text-gray-500 py-2 text-center">Tous les utilisateurs sont membres</p>
|
||||||
) : (
|
) : (
|
||||||
availableUsers.map(user => (
|
availableUsers.map(user => (
|
||||||
<div key={user.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
<div key={user.id} className="flex items-center justify-between p-2 bg-gray-50 rounded-md hover:bg-gray-100 transition-colors">
|
||||||
<span className="text-sm text-gray-700 truncate">
|
<span className="text-sm text-gray-900 font-medium truncate flex-1">
|
||||||
{user.firstName} {user.lastName}
|
{user.firstName} {user.lastName}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@ -1073,12 +1167,13 @@ export default function EquipePage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => addMemberToGroup(user.id)}
|
onClick={() => addMemberToGroup(user.id)}
|
||||||
disabled={actionLoading === user.id}
|
disabled={actionLoading === user.id}
|
||||||
className="h-6 w-6 p-0 hover:text-green-600"
|
className="h-7 w-7 p-0 hover:bg-green-50 hover:text-green-600 text-gray-500"
|
||||||
|
title="Ajouter au groupe"
|
||||||
>
|
>
|
||||||
{actionLoading === user.id ? (
|
{actionLoading === user.id ? (
|
||||||
<Loader2 className="h-3 w-3 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user