From aee5d74445a3659f728e8b31e624423522956b61 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 27 Aug 2025 11:51:43 +0200 Subject: [PATCH] feat: importance in db and mentorcard home colors --- .../skills/[skillId]/importance/route.ts | 37 ++++++++ clients/domains/admin-client.ts | 8 ++ components/admin/skills/importance-badge.tsx | 89 +++++++++++++++++++ components/admin/skills/skills-list.tsx | 11 +++ .../admin/skills/skills-management-page.tsx | 9 ++ components/home/mentor-section.tsx | 67 ++++++-------- hooks/use-skills-management.ts | 36 +++++++- lib/tech-colors.ts | 79 ++++++---------- lib/types.ts | 1 + scripts/init.sql | 2 + scripts/migrations/add_skill_importance.sql | 72 +++++++++++++++ scripts/populate_skill_importance.sql | 39 ++++++++ services/skills-service.ts | 32 ++++++- 13 files changed, 388 insertions(+), 94 deletions(-) create mode 100644 app/api/admin/skills/[skillId]/importance/route.ts create mode 100644 components/admin/skills/importance-badge.tsx create mode 100644 scripts/migrations/add_skill_importance.sql create mode 100644 scripts/populate_skill_importance.sql diff --git a/app/api/admin/skills/[skillId]/importance/route.ts b/app/api/admin/skills/[skillId]/importance/route.ts new file mode 100644 index 0000000..cd6021f --- /dev/null +++ b/app/api/admin/skills/[skillId]/importance/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from "next/server"; +import { SkillsService } from "@/services/skills-service"; + +export async function PUT( + request: NextRequest, + { params }: { params: { skillId: string } } +) { + try { + const { importance } = await request.json(); + const { skillId } = params; + + // Validation + if ( + !importance || + !["incontournable", "majeure", "standard"].includes(importance) + ) { + return NextResponse.json( + { + error: + "Importance invalide. Doit être 'incontournable', 'majeure' ou 'standard'", + }, + { status: 400 } + ); + } + + // Mettre à jour l'importance + await SkillsService.updateSkillImportance(skillId, importance); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error updating skill importance:", error); + return NextResponse.json( + { error: "Erreur lors de la mise à jour de l'importance" }, + { status: 500 } + ); + } +} diff --git a/clients/domains/admin-client.ts b/clients/domains/admin-client.ts index 1bfdc02..9ead13d 100644 --- a/clients/domains/admin-client.ts +++ b/clients/domains/admin-client.ts @@ -8,6 +8,7 @@ export interface Skill { categoryId: string; category: string; usageCount: number; + importance: string; } export interface Team { @@ -59,6 +60,13 @@ export class AdminClient extends BaseHttpClient { await this.delete(`/admin/skills/categories/${categoryId}`); } + async updateSkillImportance( + skillId: string, + importance: "incontournable" | "majeure" | "standard" + ): Promise { + await this.put(`/admin/skills/${skillId}/importance`, { importance }); + } + // Teams Management async getTeams(): Promise { return await this.get(`/admin/teams`); diff --git a/components/admin/skills/importance-badge.tsx b/components/admin/skills/importance-badge.tsx new file mode 100644 index 0000000..46ee81c --- /dev/null +++ b/components/admin/skills/importance-badge.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useState } from "react"; +import { ChevronDown, Check } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { getImportanceColors } from "@/lib/tech-colors"; + +interface ImportanceBadgeProps { + importance: string; + onImportanceChange: (newImportance: string) => void; + disabled?: boolean; +} + +export function ImportanceBadge({ + importance, + onImportanceChange, + disabled = false, +}: ImportanceBadgeProps) { + const [isOpen, setIsOpen] = useState(false); + const colors = getImportanceColors(importance); + + const handleImportanceChange = (newImportance: string) => { + onImportanceChange(newImportance); + setIsOpen(false); + }; + + const IMPORTANCE_LABELS = { + incontournable: "Incontournable", + majeure: "Majeure", + standard: "Standard", + }; + + return ( + + + + + + {!disabled && ( + + {Object.entries(IMPORTANCE_LABELS).map(([key, label]) => { + const itemColors = getImportanceColors(key); + return ( + handleImportanceChange(key)} + className="flex items-center gap-2" + > + + {label} + {importance === key && } + + ); + })} + + )} + + ); +} diff --git a/components/admin/skills/skills-list.tsx b/components/admin/skills/skills-list.tsx index 502650a..d1d4d3c 100644 --- a/components/admin/skills/skills-list.tsx +++ b/components/admin/skills/skills-list.tsx @@ -12,6 +12,7 @@ import { import { TreeCategoryHeader, TreeItemRow } from "@/components/admin"; import { TechIcon } from "@/components/icons/tech-icon"; import { Skill } from "@/clients/domains/admin-client"; +import { ImportanceBadge } from "./importance-badge"; interface SkillsListProps { filteredSkillsByCategory: Record; @@ -20,6 +21,7 @@ interface SkillsListProps { onEditSkill: (skill: Skill) => void; onDeleteSkill: (skillId: string) => void; onDeleteCategory?: (categoryName: string) => void; // Nouvelle prop + onImportanceChange?: (skillId: string, newImportance: string) => void; } export function SkillsList({ @@ -29,6 +31,7 @@ export function SkillsList({ onEditSkill, onDeleteSkill, onDeleteCategory, + onImportanceChange, }: SkillsListProps) { // Fonction pour obtenir l'icône de la catégorie const getCategoryIcon = (category: string) => { @@ -108,6 +111,14 @@ export function SkillsList({ onDelete={() => onDeleteSkill(skill.id)} canDelete={skill.usageCount === 0} showSeparator={skillIndex > 0} + additionalInfo={ + + onImportanceChange?.(skill.id, newImportance) + } + /> + } /> ))} diff --git a/components/admin/skills/skills-management-page.tsx b/components/admin/skills/skills-management-page.tsx index e40f41f..e9e5429 100644 --- a/components/admin/skills/skills-management-page.tsx +++ b/components/admin/skills/skills-management-page.tsx @@ -51,6 +51,7 @@ export function SkillsManagementPage({ handleEditSkill, handleUpdateSkill, handleDeleteSkill, + handleImportanceChange, } = useSkillsManagement(skillCategories, initialSkills); // Utilisation du hook factorisé pour la vue arborescente @@ -93,6 +94,13 @@ export function SkillsManagementPage({ openEditDialog(); }; + const handleImportanceChangeWrapper = async ( + skillId: string, + newImportance: string + ) => { + await handleImportanceChange(skillId, newImportance); + }; + const headerActions = (