"use client"; import { useState, useEffect, useMemo } from "react"; import { Plus, Edit, Trash2, Code2, Search, Folder, FolderOpen, ChevronRight, ChevronDown, Palette, Database, Cloud, Shield, Smartphone, Layers, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/hooks/use-toast"; import { SkillCategory, Team } from "@/lib/types"; import { AdminManagementService, Skill, } from "@/services/admin-management-service"; import { TechIcon } from "@/components/icons/tech-icon"; import { TreeViewContainer, TreeCategoryHeader, TreeItemRow, TreeSearchControls, } from "@/components/admin"; interface SkillsManagementProps { skillCategories: SkillCategory[]; teams: Team[]; } interface SkillFormData { name: string; categoryId: string; description: string; icon: string; } export function SkillsManagement({ skillCategories, teams, }: SkillsManagementProps) { const [searchTerm, setSearchTerm] = useState(""); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [editingSkill, setEditingSkill] = useState(null); const [skillFormData, setSkillFormData] = useState({ name: "", categoryId: "", description: "", icon: "", }); const { toast } = useToast(); // État des skills const [skills, setSkills] = useState([]); const [isLoading, setIsLoading] = useState(true); // État pour les catégories ouvertes/fermées const [expandedCategories, setExpandedCategories] = useState>( new Set() ); // Grouper les skills par catégorie et filtrer en fonction de la recherche const filteredSkillsByCategory = useMemo(() => { // Grouper les skills par catégorie const skillsByCategory = skills.reduce((acc, skill) => { if (!acc[skill.category]) { acc[skill.category] = []; } acc[skill.category].push(skill); return acc; }, {} as Record); // Filtrer les skills en fonction de la recherche return Object.entries(skillsByCategory).reduce( (acc, [category, categorySkills]) => { const filteredSkills = categorySkills.filter((skill) => { const matchesSearch = skill.name.toLowerCase().includes(searchTerm.toLowerCase()) || (skill.description && skill.description .toLowerCase() .includes(searchTerm.toLowerCase())); return matchesSearch; }); if (filteredSkills.length > 0) { acc[category] = filteredSkills; } return acc; }, {} as Record ); }, [skills, searchTerm]); // Fonctions pour gérer l'expansion des catégories const toggleCategory = useMemo( () => (category: string) => { setExpandedCategories((prev) => { const newExpanded = new Set(prev); if (newExpanded.has(category)) { newExpanded.delete(category); } else { newExpanded.add(category); } return newExpanded; }); }, [] ); const expandAll = useMemo( () => () => { setExpandedCategories(new Set(Object.keys(filteredSkillsByCategory))); }, [filteredSkillsByCategory] ); const collapseAll = useMemo( () => () => { setExpandedCategories(new Set()); }, [] ); // Charger les skills depuis l'API const fetchSkills = async () => { try { setIsLoading(true); const skillsData = await AdminManagementService.getSkills(); setSkills(skillsData); } catch (error) { console.error("Error fetching skills:", error); toast({ title: "Erreur", description: "Impossible de charger les skills", variant: "destructive", }); } finally { setIsLoading(false); } }; // Charger les skills au montage du composant useEffect(() => { fetchSkills(); }, []); // Ouvrir automatiquement les catégories qui contiennent des résultats lors de la recherche useEffect(() => { if (searchTerm.trim()) { const categoriesWithResults = Object.keys(filteredSkillsByCategory); setExpandedCategories(new Set(categoriesWithResults)); } else { // Si pas de recherche, fermer toutes les catégories setExpandedCategories(new Set()); } }, [searchTerm, filteredSkillsByCategory]); const handleCreateSkill = async () => { if (!skillFormData.name || !skillFormData.categoryId) { toast({ title: "Erreur", description: "Veuillez remplir tous les champs obligatoires", variant: "destructive", }); return; } try { const categoryIndex = parseInt(skillFormData.categoryId); const category = skillCategories[categoryIndex]; const skillData = { ...skillFormData, category: category.category, }; const newSkill = await AdminManagementService.createSkill(skillData); setSkills([...skills, newSkill]); setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); setIsCreateDialogOpen(false); toast({ title: "Succès", description: "Skill créée avec succès", }); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la création de la skill", variant: "destructive", }); } }; const handleEditSkill = (skill: any) => { setEditingSkill(skill); const categoryIndex = skillCategories.findIndex( (cat) => cat.category === skill.category ); setSkillFormData({ name: skill.name, categoryId: categoryIndex !== -1 ? categoryIndex.toString() : "", description: skill.description, icon: skill.icon, }); setIsEditDialogOpen(true); }; const handleUpdateSkill = async () => { if (!editingSkill || !skillFormData.name || !skillFormData.categoryId) { toast({ title: "Erreur", description: "Veuillez remplir tous les champs obligatoires", variant: "destructive", }); return; } try { const categoryIndex = parseInt(skillFormData.categoryId); const category = skillCategories[categoryIndex]; const skillData = { id: editingSkill.id, ...skillFormData, category: category.category, usageCount: editingSkill.usageCount, }; const updatedSkill = await AdminManagementService.updateSkill(skillData); const updatedSkills = skills.map((skill) => skill.id === editingSkill.id ? updatedSkill : skill ); setSkills(updatedSkills); setIsEditDialogOpen(false); setEditingSkill(null); setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); toast({ title: "Succès", description: "Skill mise à jour avec succès", }); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la mise à jour de la skill", variant: "destructive", }); } }; const handleDeleteSkill = async (skillId: string) => { if ( confirm( "Êtes-vous sûr de vouloir supprimer cette skill ? Cette action est irréversible." ) ) { try { await AdminManagementService.deleteSkill(skillId); setSkills(skills.filter((s) => s.id !== skillId)); toast({ title: "Succès", description: "Skill supprimée avec succès", }); } catch (error: any) { toast({ title: "Erreur", description: error.message || "Erreur lors de la suppression de la skill", variant: "destructive", }); } } }; const resetForm = () => { setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); }; // Fonction pour obtenir l'icône de la catégorie const getCategoryIcon = (category: string) => { const categoryName = category.toLowerCase(); if (categoryName.includes("frontend") || categoryName.includes("front")) return Code2; if (categoryName.includes("backend") || categoryName.includes("back")) return Layers; if ( categoryName.includes("design") || categoryName.includes("ui") || categoryName.includes("ux") ) return Palette; if (categoryName.includes("data") || categoryName.includes("database")) return Database; if (categoryName.includes("cloud") || categoryName.includes("devops")) return Cloud; if (categoryName.includes("security") || categoryName.includes("securité")) return Shield; if ( categoryName.includes("mobile") || categoryName.includes("android") || categoryName.includes("ios") ) return Smartphone; return Code2; // Par défaut }; return (
{/* Header */}

Gestion des Skills

Créez, modifiez et supprimez les skills de votre organisation

Créer une nouvelle skill
setSkillFormData({ ...skillFormData, name: e.target.value }) } placeholder="Ex: React, Node.js, PostgreSQL" />