This commit is contained in:
alma 2025-05-05 10:38:37 +02:00
parent cefc9fd072
commit a1cb75a1ec

View File

@ -18,22 +18,25 @@ import {
} from "../ui/card";
import { Badge } from "../ui/badge";
import { X, Search, UserPlus, Users } from "lucide-react";
import { toast } from "../ui/use-toast";
// Mock user data - in a real app, this would come from an API
const mockUsers = [
{ id: '1', username: 'user1', firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
{ id: '2', username: 'user2', firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
{ id: '3', username: 'user3', firstName: 'Alice', lastName: 'Johnson', email: 'alice@example.com' },
{ id: '4', username: 'user4', firstName: 'Bob', lastName: 'Brown', email: 'bob@example.com' },
{ id: '5', username: 'user5', firstName: 'Eva', lastName: 'Martinez', email: 'eva@example.com' },
];
// Define interfaces for user and group data
interface User {
id: string;
username: string;
firstName: string;
lastName: string;
email: string;
roles?: string[];
enabled?: boolean;
}
// Mock group data
const mockGroups = [
{ id: '1', name: 'Group A', path: '/group-a', membersCount: 3 },
{ id: '2', name: 'Group B', path: '/group-b', membersCount: 5 },
{ id: '3', name: 'Group C', path: '/group-c', membersCount: 2 },
];
interface Group {
id: string;
name: string;
path: string;
membersCount: number;
}
export function MissionsAdminPanel() {
const [selectedServices, setSelectedServices] = useState<string[]>([]);
@ -43,21 +46,101 @@ export function MissionsAdminPanel() {
const [gardienDuTemps, setGardienDuTemps] = useState<string | null>(null);
const [gardienDeLaParole, setGardienDeLaParole] = useState<string | null>(null);
const [gardienDeLaMemoire, setGardienDeLaMemoire] = useState<string | null>(null);
// State for storing fetched data
const [users, setUsers] = useState<User[]>([]);
const [groups, setGroups] = useState<Group[]>([]);
const [loading, setLoading] = useState(true);
// Fetch users and groups on component mount
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
await Promise.all([fetchUsers(), fetchGroups()]);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
// Function to fetch users from API
const fetchUsers = async () => {
try {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error("Failed to fetch 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",
});
}
};
// Function to fetch groups from API
const fetchGroups = async () => {
try {
const response = await fetch("/api/groups");
if (!response.ok) {
throw new Error("Failed to fetch groups");
}
const data = await response.json();
// Fetch member counts for groups
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, membersCount: 0};
} catch (error) {
console.error(`Error fetching members for group ${group.id}:`, error);
return {...group, membersCount: 0};
}
})
);
setGroups(groupsWithCounts);
} catch (error) {
console.error("Error fetching groups:", error);
toast({
title: "Erreur",
description: "Erreur lors de la récupération des groupes",
variant: "destructive",
});
}
};
// Filtered users based on search term
const filteredUsers = mockUsers.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())
const filteredUsers = users.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())
);
// Filtered groups based on search term
const filteredGroups = mockGroups.filter(group =>
group.name.toLowerCase().includes(searchTerm.toLowerCase())
const filteredGroups = groups.filter(group =>
(group.name?.toLowerCase() || "").includes(searchTerm.toLowerCase())
);
// Function to check if a user is already selected for a role
// Function to check if a user is already assigned for a role
const isUserAssigned = (userId: string) => {
return gardienDuTemps === userId ||
gardienDeLaParole === userId ||
@ -94,6 +177,44 @@ export function MissionsAdminPanel() {
}
};
// Function to fetch group members
const fetchGroupMembers = async (groupId: string) => {
try {
const response = await fetch(`/api/groups/${groupId}/members`);
if (!response.ok) {
throw new Error("Failed to fetch group members");
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching members for group ${groupId}:`, error);
toast({
title: "Erreur",
description: "Erreur lors de la récupération des membres du groupe",
variant: "destructive",
});
return [];
}
};
// Handler for viewing group members
const handleViewGroupMembers = async (groupId: string, groupName: string) => {
try {
setLoading(true);
const members = await fetchGroupMembers(groupId);
// Here you would typically open a dialog to show members
// For this implementation, we'll just show a toast with the count
toast({
title: `Membres de ${groupName}`,
description: `${members.length} membres trouvés dans ce groupe`,
});
} catch (error) {
console.error("Error handling group members:", error);
} finally {
setLoading(false);
}
};
return (
<div className="w-full">
<Card className="border shadow-sm bg-white">
@ -524,36 +645,49 @@ export function MissionsAdminPanel() {
size="sm"
onClick={() => removeUserRole('temps')}
className="text-red-600 hover:bg-red-50 border-red-200 h-8"
disabled={loading}
>
<X size={16} className="mr-1" />
Supprimer
</Button>
)}
</div>
{gardienDuTemps ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = mockUsers.find(u => u.id === gardienDuTemps);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName[0]}{user.lastName[0]}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
{loading ? (
<div className="flex items-center py-2 px-3 bg-gray-50 border border-gray-200 rounded-md">
<div className="animate-pulse w-full flex items-center">
<div className="h-10 w-10 bg-gray-300 rounded-full mr-3"></div>
<div className="flex-1 space-y-2">
<div className="h-3 bg-gray-300 rounded w-1/3"></div>
<div className="h-3 bg-gray-300 rounded w-1/2"></div>
</div>
</div>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
gardienDuTemps ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = users.find(u => u.id === gardienDuTemps);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
)
)}
</div>
@ -567,36 +701,49 @@ export function MissionsAdminPanel() {
size="sm"
onClick={() => removeUserRole('parole')}
className="text-red-600 hover:bg-red-50 border-red-200 h-8"
disabled={loading}
>
<X size={16} className="mr-1" />
Supprimer
</Button>
)}
</div>
{gardienDeLaParole ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = mockUsers.find(u => u.id === gardienDeLaParole);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName[0]}{user.lastName[0]}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
{loading ? (
<div className="flex items-center py-2 px-3 bg-gray-50 border border-gray-200 rounded-md">
<div className="animate-pulse w-full flex items-center">
<div className="h-10 w-10 bg-gray-300 rounded-full mr-3"></div>
<div className="flex-1 space-y-2">
<div className="h-3 bg-gray-300 rounded w-1/3"></div>
<div className="h-3 bg-gray-300 rounded w-1/2"></div>
</div>
</div>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
gardienDeLaParole ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = users.find(u => u.id === gardienDeLaParole);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
)
)}
</div>
@ -610,36 +757,49 @@ export function MissionsAdminPanel() {
size="sm"
onClick={() => removeUserRole('memoire')}
className="text-red-600 hover:bg-red-50 border-red-200 h-8"
disabled={loading}
>
<X size={16} className="mr-1" />
Supprimer
</Button>
)}
</div>
{gardienDeLaMemoire ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = mockUsers.find(u => u.id === gardienDeLaMemoire);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName[0]}{user.lastName[0]}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
{loading ? (
<div className="flex items-center py-2 px-3 bg-gray-50 border border-gray-200 rounded-md">
<div className="animate-pulse w-full flex items-center">
<div className="h-10 w-10 bg-gray-300 rounded-full mr-3"></div>
<div className="flex-1 space-y-2">
<div className="h-3 bg-gray-300 rounded w-1/3"></div>
<div className="h-3 bg-gray-300 rounded w-1/2"></div>
</div>
</div>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
gardienDeLaMemoire ? (
<div className="bg-blue-50 border border-blue-100 rounded-md p-3">
{(() => {
const user = users.find(u => u.id === gardienDeLaMemoire);
return user ? (
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-medium mr-3">
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
) : "Utilisateur non trouvé";
})()}
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
) : (
<div className="flex items-center text-gray-500 bg-gray-50 border border-gray-200 rounded-md py-2 px-3">
<div className="h-8 w-8 rounded-full bg-gray-200 flex items-center justify-center mr-3">
<Users size={14} className="text-gray-400" />
</div>
<span className="text-sm">Aucun utilisateur sélectionné</span>
</div>
)
)}
</div>
</div>
@ -655,6 +815,7 @@ export function MissionsAdminPanel() {
size="sm"
onClick={() => setSelectedTab('users')}
className={selectedTab === 'users' ? 'bg-blue-600 text-white' : 'text-gray-700'}
disabled={loading}
>
<Users size={16} className="mr-1" />
Utilisateurs
@ -664,6 +825,7 @@ export function MissionsAdminPanel() {
size="sm"
onClick={() => setSelectedTab('groups')}
className={selectedTab === 'groups' ? 'bg-blue-600 text-white' : 'text-gray-700'}
disabled={loading}
>
<Users size={16} className="mr-1" />
Groupes
@ -679,100 +841,121 @@ export function MissionsAdminPanel() {
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-9 bg-white text-gray-900 border-gray-300"
disabled={loading}
/>
</div>
<div className="border rounded-md max-h-[300px] overflow-y-auto">
{selectedTab === 'users' ? (
filteredUsers.length > 0 ? (
<div className="divide-y divide-gray-200">
{filteredUsers.map(user => (
<div key={user.id} className="p-3 hover:bg-gray-50 flex items-center justify-between">
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 font-medium mr-3">
{user.firstName[0]}{user.lastName[0]}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
{isUserAssigned(user.id) ? (
{loading ? (
<div className="border rounded-md p-6 flex flex-col items-center justify-center text-gray-500">
<div className="animate-pulse space-y-4 w-full">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center py-3 px-4">
<div className="h-10 w-10 bg-gray-300 rounded-full mr-3"></div>
<div className="flex-1 space-y-2">
<div className="h-3 bg-gray-300 rounded w-1/3"></div>
<div className="h-3 bg-gray-300 rounded w-1/2"></div>
</div>
<div className="h-7 w-16 bg-gray-300 rounded"></div>
</div>
))}
</div>
</div>
) : (
<div className="border rounded-md max-h-[300px] overflow-y-auto">
{selectedTab === 'users' ? (
filteredUsers.length > 0 ? (
<div className="divide-y divide-gray-200">
{filteredUsers.map(user => (
<div key={user.id} className="p-3 hover:bg-gray-50 flex items-center justify-between">
<div className="flex items-center">
<Badge className="bg-blue-100 text-blue-800 hover:bg-blue-200 px-2 py-1 mr-2">
{getUserRole(user.id)}
</Badge>
<Button
variant="outline"
size="sm"
onClick={() => {
if (gardienDuTemps === user.id) removeUserRole('temps');
if (gardienDeLaParole === user.id) removeUserRole('parole');
if (gardienDeLaMemoire === user.id) removeUserRole('memoire');
}}
className="text-red-600 hover:bg-red-50 border-red-200 h-7 px-2"
>
<X size={14} />
</Button>
</div>
) : (
<div className="flex space-x-1">
<Button
variant="outline"
size="sm"
onClick={() => {
if (!gardienDuTemps) assignUserRole(user.id, 'temps');
else if (!gardienDeLaParole) assignUserRole(user.id, 'parole');
else if (!gardienDeLaMemoire) assignUserRole(user.id, 'memoire');
}}
disabled={gardienDuTemps !== null && gardienDeLaParole !== null && gardienDeLaMemoire !== null}
className="text-blue-600 hover:bg-blue-50 border-blue-200 h-8"
>
<UserPlus size={16} className="mr-1" />
Assigner
</Button>
</div>
)}
</div>
))}
</div>
) : (
<div className="p-4 text-center text-gray-500">
Aucun utilisateur trouvé
</div>
)
) : (
filteredGroups.length > 0 ? (
<div className="divide-y divide-gray-200">
{filteredGroups.map(group => (
<div key={group.id} className="p-3 hover:bg-gray-50 flex items-center justify-between">
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 font-medium mr-3">
<Users size={16} />
</div>
<div>
<div className="font-medium text-gray-900">{group.name}</div>
<div className="text-sm text-gray-500">{group.membersCount} membres</div>
<div className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 font-medium mr-3">
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
</div>
<div>
<div className="font-medium text-gray-900">{user.firstName} {user.lastName}</div>
<div className="text-sm text-gray-500">{user.email}</div>
</div>
</div>
{isUserAssigned(user.id) ? (
<div className="flex items-center">
<Badge className="bg-blue-100 text-blue-800 hover:bg-blue-200 px-2 py-1 mr-2">
{getUserRole(user.id)}
</Badge>
<Button
variant="outline"
size="sm"
onClick={() => {
if (gardienDuTemps === user.id) removeUserRole('temps');
if (gardienDeLaParole === user.id) removeUserRole('parole');
if (gardienDeLaMemoire === user.id) removeUserRole('memoire');
}}
className="text-red-600 hover:bg-red-50 border-red-200 h-7 px-2"
disabled={loading}
>
<X size={14} />
</Button>
</div>
) : (
<div className="flex space-x-1">
<Button
variant="outline"
size="sm"
onClick={() => {
if (!gardienDuTemps) assignUserRole(user.id, 'temps');
else if (!gardienDeLaParole) assignUserRole(user.id, 'parole');
else if (!gardienDeLaMemoire) assignUserRole(user.id, 'memoire');
}}
disabled={(gardienDuTemps !== null && gardienDeLaParole !== null && gardienDeLaMemoire !== null) || loading}
className="text-blue-600 hover:bg-blue-50 border-blue-200 h-8"
>
<UserPlus size={16} className="mr-1" />
Assigner
</Button>
</div>
)}
</div>
<Button
variant="outline"
size="sm"
className="text-blue-600 hover:bg-blue-50 border-blue-200 h-8"
>
<Users size={16} className="mr-1" />
Voir membres
</Button>
</div>
))}
</div>
))}
</div>
) : (
<div className="p-4 text-center text-gray-500">
Aucun utilisateur trouvé
</div>
)
) : (
<div className="p-4 text-center text-gray-500">
Aucun groupe trouvé
</div>
)
)}
</div>
filteredGroups.length > 0 ? (
<div className="divide-y divide-gray-200">
{filteredGroups.map(group => (
<div key={group.id} className="p-3 hover:bg-gray-50 flex items-center justify-between">
<div className="flex items-center">
<div className="h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 font-medium mr-3">
<Users size={16} />
</div>
<div>
<div className="font-medium text-gray-900">{group.name}</div>
<div className="text-sm text-gray-500">{group.membersCount} membres</div>
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleViewGroupMembers(group.id, group.name)}
className="text-blue-600 hover:bg-blue-50 border-blue-200 h-8"
disabled={loading}
>
<Users size={16} className="mr-1" />
Voir membres
</Button>
</div>
))}
</div>
) : (
<div className="p-4 text-center text-gray-500">
Aucun groupe trouvé
</div>
)
)}
</div>
)}
</div>
</div>
</div>