From 5cb2bad99261a44b315b78287687137ef66701df Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 21 Aug 2025 11:46:29 +0200 Subject: [PATCH] feat: add skill creation functionality and reload categories - Introduced a new button to create skills within the SkillSelector component, allowing users to add missing skills directly. - Implemented a dialog for skill creation, including a form to handle new skill submissions. - Added a reloadSkillCategories function in the useEvaluation hook to refresh skill categories and migrate existing evaluations if necessary. - Enhanced error handling for category reloading to improve robustness. --- components/create-skill-form.tsx | 214 +++++++++++++++++++++++++++++++ components/skill-selector.tsx | 38 +++++- hooks/use-evaluation.ts | 19 +++ 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 components/create-skill-form.tsx diff --git a/components/create-skill-form.tsx b/components/create-skill-form.tsx new file mode 100644 index 0000000..3af5bef --- /dev/null +++ b/components/create-skill-form.tsx @@ -0,0 +1,214 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { Plus, X, Link as LinkIcon, Loader2 } from "lucide-react"; +import { apiClient } from "@/services/client"; + +interface CreateSkillFormProps { + categoryName: string; + onSuccess: (skillId: string) => void; + onCancel: () => void; +} + +export function CreateSkillForm({ + categoryName, + onSuccess, + onCancel, +}: CreateSkillFormProps) { + const [formData, setFormData] = useState({ + name: "", + description: "", + icon: "", + links: [""], + }); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + const addLink = () => { + setFormData(prev => ({ + ...prev, + links: [...prev.links, ""] + })); + }; + + const removeLink = (index: number) => { + setFormData(prev => ({ + ...prev, + links: prev.links.filter((_, i) => i !== index) + })); + }; + + const updateLink = (index: number, value: string) => { + setFormData(prev => ({ + ...prev, + links: prev.links.map((link, i) => i === index ? value : link) + })); + }; + + const generateSkillId = (name: string) => { + return name + .toLowerCase() + .replace(/[^a-z0-9\s]/g, '') + .replace(/\s+/g, '-') + .trim(); + }; + + const getCategoryId = (categoryName: string) => { + // Convertir le nom de catégorie en ID (ex: "Cloud" -> "cloud") + return categoryName.toLowerCase(); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + try { + // Validation + if (!formData.name.trim()) { + throw new Error("Le nom de la compétence est requis"); + } + if (!formData.description.trim()) { + throw new Error("La description est requise"); + } + + const skillId = generateSkillId(formData.name); + const categoryId = getCategoryId(categoryName); + + // Filtrer les liens vides + const validLinks = formData.links.filter(link => link.trim()); + + const skillData = { + id: skillId, + name: formData.name.trim(), + description: formData.description.trim(), + icon: formData.icon.trim() || "fas-cog", + links: validLinks, + }; + + const success = await apiClient.createSkill(categoryId, skillData); + + if (success) { + onSuccess(skillId); + } else { + throw new Error("Erreur lors de la création de la compétence"); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Une erreur est survenue"); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ {error && ( +
+ {error} +
+ )} + +
+ + setFormData(prev => ({ ...prev, name: e.target.value }))} + disabled={isLoading} + /> +
+ +
+ +