"use client"; import { useState, useEffect } from "react"; import { Plus, Edit, Trash2, Users, Building2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; import { SkillCategory, Team as TeamType } from "@/lib/types"; import { TeamStats } from "@/services/admin-service"; import { AdminManagementService, Team, } from "@/services/admin-management-service"; import { TreeCategoryHeader, TreeItemRow, TeamMetrics, } from "@/components/admin"; import { TeamMembersModal } from "@/components/admin/management/team-members-modal"; import { TreeViewPage } from "../tree-view-page"; import { useTreeView } from "@/hooks/use-tree-view"; import { useFormDialog } from "@/hooks/use-form-dialog"; interface TeamsManagementProps { teams: TeamType[]; teamStats: TeamStats[]; skillCategories: SkillCategory[]; } interface TeamFormData { name: string; direction: string; } export function TeamsManagement({ teams, teamStats, skillCategories, }: TeamsManagementProps) { const [searchTerm, setSearchTerm] = useState(""); const [isMembersModalOpen, setIsMembersModalOpen] = useState(false); const [selectedTeam, setSelectedTeam] = useState(null); const [editingTeam, setEditingTeam] = useState(null); const [teamFormData, setTeamFormData] = useState({ name: "", direction: "", }); const { toast } = useToast(); const { isCreateDialogOpen, isEditDialogOpen, openCreateDialog, closeCreateDialog, openEditDialog, closeEditDialog } = useFormDialog(); // État local pour les équipes et leurs stats const [localTeams, setLocalTeams] = useState(teams); const [localTeamStats, setLocalTeamStats] = useState(teamStats); // Utilisation du hook factorisé const { filteredDataByCategory: filteredTeamsByDirection, expandedCategories: expandedDirections, toggleCategory: toggleDirection, expandAll, collapseAll, } = useTreeView({ data: localTeams, searchFields: ['name'], groupBy: (team) => team.direction, searchTerm, onSearchChange: setSearchTerm, }); const getTeamStats = (teamId: string): TeamStats | undefined => { return localTeamStats.find((stats) => stats.teamId === teamId); }; // Fonction pour obtenir une équipe depuis la liste locale const getLocalTeam = (teamId: string): TeamType | undefined => { return localTeams.find((team) => team.id === teamId); }; // Charger les teams depuis l'API const fetchTeams = async () => { try { const teamsData = await AdminManagementService.getTeams(); // Note: on garde les teams existantes pour la compatibilité // Les nouvelles teams créées via l'API seront visibles après rafraîchissement } catch (error) { console.error("Error fetching teams:", error); toast({ title: "Erreur", description: "Impossible de charger les teams", variant: "destructive", }); } }; // Charger les teams au montage du composant useEffect(() => { fetchTeams(); }, []); const handleCreateTeam = async () => { if (!teamFormData.name || !teamFormData.direction) { toast({ title: "Erreur", description: "Veuillez remplir tous les champs obligatoires", variant: "destructive", }); return; } try { const newTeam = await AdminManagementService.createTeam(teamFormData); toast({ title: "Succès", description: "Équipe créée avec succès", }); setTeamFormData({ name: "", direction: "" }); closeCreateDialog(); // Mettre à jour l'état local avec la nouvelle équipe const newLocalTeam: TeamType = { id: newTeam.id, name: newTeam.name, direction: newTeam.direction, }; setLocalTeams((prev) => [...prev, newLocalTeam]); // Ajouter les stats de la nouvelle équipe (avec les propriétés minimales) const newTeamStats = { teamId: newTeam.id, teamName: newTeam.name, direction: newTeam.direction, totalMembers: 0, averageSkillLevel: 0, skillCoverage: 0, topSkills: [], members: [], } as TeamStats; setLocalTeamStats((prev) => [...prev, newTeamStats]); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la création de l'équipe", variant: "destructive", }); } }; const handleEditTeam = (team: any) => { setEditingTeam(team); setTeamFormData({ name: team.name, direction: team.direction, }); openEditDialog(); }; const handleViewMembers = (team: any) => { setSelectedTeam(team); setIsMembersModalOpen(true); }; // Fonction pour mettre à jour les stats d'une équipe après suppression d'un membre const updateTeamStatsAfterMemberRemoval = (teamId: string) => { setLocalTeamStats((prev) => { const updated = prev.map((stats) => stats.teamId === teamId ? { ...stats, totalMembers: Math.max(0, stats.totalMembers - 1) } : stats ); return updated; }); }; const handleUpdateTeam = async () => { if (!editingTeam || !teamFormData.name || !teamFormData.direction) { toast({ title: "Erreur", description: "Veuillez remplir tous les champs obligatoires", variant: "destructive", }); return; } try { await AdminManagementService.updateTeam({ id: editingTeam.id, ...teamFormData, memberCount: editingTeam.memberCount || 0, }); toast({ title: "Succès", description: "Équipe mise à jour avec succès", }); closeEditDialog(); setEditingTeam(null); setTeamFormData({ name: "", direction: "" }); // Mettre à jour l'état local au lieu de recharger la page // On pourrait mettre à jour les données locales ici si nécessaire // Pour l'instant, on laisse l'utilisateur voir le changement } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la mise à jour de l'équipe", variant: "destructive", }); } }; const handleDeleteTeam = async (teamId: string) => { const team = getLocalTeam(teamId); const stats = getTeamStats(teamId); if (stats && stats.totalMembers > 0) { toast({ title: "Erreur", description: "Impossible de supprimer une équipe qui contient des membres", variant: "destructive", }); return; } if ( confirm( `Êtes-vous sûr de vouloir supprimer l'équipe "${team?.name}" ? Cette action est irréversible.` ) ) { try { await AdminManagementService.deleteTeam(teamId); toast({ title: "Succès", description: "Équipe supprimée avec succès", }); // Mettre à jour l'état local au lieu de recharger la page // Supprimer l'équipe de la liste locale setLocalTeams((prev) => prev.filter((t) => t.id !== teamId)); // Mettre à jour les stats locales setLocalTeamStats((prev) => prev.filter((stats) => stats.teamId !== teamId) ); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la suppression de l'équipe", variant: "destructive", }); } } }; const handleDeleteDirection = async (direction: string) => { // Vérifier si des équipes de cette direction ont des membres const teamsInDirection = teams.filter( (team) => team.direction === direction ); const hasMembers = teamsInDirection.some((team) => { const stats = getTeamStats(team.id); return stats && stats.totalMembers > 0; }); if (hasMembers) { toast({ title: "Erreur", description: `Impossible de supprimer la direction "${direction}" car certaines équipes ont des membres`, variant: "destructive", }); return; } if ( confirm( `Êtes-vous sûr de vouloir supprimer la direction "${direction}" et TOUTES ses équipes ?\n\n⚠️ Cette action est irréversible !\n\nÉquipes qui seront supprimées :\n${teamsInDirection .map((t) => `• ${t.name}`) .join("\n")}` ) ) { try { await AdminManagementService.deleteDirection(direction); toast({ title: "Succès", description: `Direction "${direction}" et toutes ses équipes supprimées avec succès`, variant: "default", }); // Mettre à jour l'état local au lieu de recharger la page // Supprimer les équipes de la direction de la liste locale setLocalTeams((prev) => prev.filter((team) => team.direction !== direction) ); // Supprimer les équipes de la direction des stats locales setLocalTeamStats((prev) => prev.filter((stats) => stats.direction !== direction) ); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la suppression de la direction", variant: "destructive", }); } } }; const resetForm = () => { setTeamFormData({ name: "", direction: "" }); }; // Extraire les directions uniques pour les formulaires const directions = Array.from( new Set(localTeams.map((team) => team.direction)) ); const headerActions = ( Créer une nouvelle équipe
setTeamFormData({ ...teamFormData, name: e.target.value }) } placeholder="Ex: Équipe Frontend, Équipe Backend" />
); const emptyState = (

{searchTerm ? "Aucune équipe trouvée" : "Aucune équipe"}

{searchTerm ? "Essayez de modifier vos critères de recherche" : "Commencez par créer votre première équipe"}

); return ( <> 0} emptyState={emptyState} headerActions={headerActions} > {Object.entries(filteredTeamsByDirection).map( ([direction, directionTeams], index) => (
toggleDirection(direction)} icon={} itemCount={directionTeams.length} itemLabel="équipe" showSeparator={index > 0} onDelete={() => handleDeleteDirection(direction)} canDelete={true} isDirection={true} /> {/* Liste des teams de la direction */} {expandedDirections.has(direction) && (
{directionTeams.map((team, teamIndex) => { const stats = getTeamStats(team.id); return ( } title={team.name} badges={ stats ? [ { text: `${stats.totalMembers} membres`, variant: "outline", }, ] : [] } onEdit={() => handleEditTeam(team)} onDelete={() => handleDeleteTeam(team.id)} onViewMembers={() => handleViewMembers(team)} canDelete={!stats || stats.totalMembers === 0} showSeparator={teamIndex > 0} hasMembers={stats ? stats.totalMembers > 0 : false} additionalInfo={ stats ? ( ) : (

Aucune donnée disponible

) } /> ); })}
)}
) )}
{/* Dialog d'édition */} Modifier l'équipe
setTeamFormData({ ...teamFormData, name: e.target.value }) } placeholder="Ex: Équipe Frontend, Équipe Backend" />
{/* Modal des membres d'équipe */} {selectedTeam && ( { setIsMembersModalOpen(false); setSelectedTeam(null); // Pas besoin de rafraîchir ici, les stats locales sont déjà à jour }} onMemberRemoved={() => { // Mettre à jour les stats localement if (selectedTeam) { updateTeamStatsAfterMemberRemoval(selectedTeam.id); } }} /> )} ); }