feat: importance in db and mentorcard home colors

This commit is contained in:
Julien Froidefond
2025-08-27 11:51:43 +02:00
parent df1fd24e84
commit aee5d74445
13 changed files with 388 additions and 94 deletions

View File

@@ -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 (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className={`h-6 px-2 text-xs font-medium border ${colors.bg} ${
colors.text
} ${colors.border} hover:opacity-80 transition-all ${
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
}`}
disabled={disabled}
>
<span
className={`w-2 h-2 rounded-full ${colors.text.replace(
"text-",
"bg-"
)}`}
/>
{IMPORTANCE_LABELS[importance as keyof typeof IMPORTANCE_LABELS] ||
importance}
{!disabled && <ChevronDown className="w-3 h-3 ml-1" />}
</Button>
</DropdownMenuTrigger>
{!disabled && (
<DropdownMenuContent align="end" className="w-40">
{Object.entries(IMPORTANCE_LABELS).map(([key, label]) => {
const itemColors = getImportanceColors(key);
return (
<DropdownMenuItem
key={key}
onClick={() => handleImportanceChange(key)}
className="flex items-center gap-2"
>
<span
className={`w-2 h-2 rounded-full ${itemColors.text.replace(
"text-",
"bg-"
)}`}
/>
{label}
{importance === key && <Check className="w-3 h-3 ml-auto" />}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
)}
</DropdownMenu>
);
}

View File

@@ -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<string, Skill[]>;
@@ -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={
<ImportanceBadge
importance={skill.importance || "standard"}
onImportanceChange={(newImportance: string) =>
onImportanceChange?.(skill.id, newImportance)
}
/>
}
/>
))}
</div>

View File

@@ -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 = (
<Button onClick={handleOpenCreateDialog}>
<Plus className="w-4 h-4 mr-2" />
@@ -137,6 +145,7 @@ export function SkillsManagementPage({
onEditSkill={handleOpenEditDialog}
onDeleteSkill={handleDeleteSkill}
onDeleteCategory={handleDeleteCategory}
onImportanceChange={handleImportanceChangeWrapper}
/>
</TreeViewPage>

View File

@@ -1,5 +1,5 @@
import { TechIcon } from "@/components/icons/tech-icon";
import { getTechColors } from "@/lib/tech-colors";
import { getImportanceColors } from "@/lib/tech-colors";
import { UserEvaluation, SkillCategory } from "@/lib/types";
interface MentorSectionProps {
@@ -13,26 +13,6 @@ export function MentorSection({
userEvaluation,
skillCategories,
}: MentorSectionProps) {
// Récupérer les compétences où l'utilisateur peut être mentor
const mentorSkills = userEvaluation.evaluations.flatMap((cat) => {
const skillCategory = skillCategories.find(
(sc) => sc.category === cat.category
);
return cat.skills
.filter((skill) => skill.canMentor)
.map((skill) => {
const skillInfo = skillCategory?.skills.find(
(s) => s.id === skill.skillId
);
return {
id: skill.skillId,
name: skillInfo?.name || skill.skillId,
icon: skillInfo?.icon || "fas-code",
level: skill.level,
};
});
});
// Récupérer les compétences maîtrisées (expert uniquement)
const masteredSkills = userEvaluation.evaluations.flatMap((cat) => {
const skillCategory = skillCategories.find(
@@ -49,6 +29,28 @@ export function MentorSection({
name: skillInfo?.name || skill.skillId,
icon: skillInfo?.icon || "fas-code",
level: skill.level,
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
};
});
});
// Récupérer les compétences où l'utilisateur peut être mentor
const mentorSkills = userEvaluation.evaluations.flatMap((cat) => {
const skillCategory = skillCategories.find(
(sc) => sc.category === cat.category
);
return cat.skills
.filter((skill) => skill.canMentor)
.map((skill) => {
const skillInfo = skillCategory?.skills.find(
(s) => s.id === skill.skillId
);
return {
id: skill.skillId,
name: skillInfo?.name || skill.skillId,
icon: skillInfo?.icon || "fas-code",
level: skill.level,
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
};
});
});
@@ -69,26 +71,11 @@ export function MentorSection({
name: skillInfo?.name || skill.skillId,
icon: skillInfo?.icon || "fas-code",
level: skill.level,
importance: skillInfo?.importance || "standard", // Récupérer l'importance depuis la base
};
});
});
// Fonction pour déterminer la couleur d'une technologie
const getTechColor = (techName: string) => {
const lowerName = techName.toLowerCase();
if (lowerName.includes("react") || lowerName.includes("next"))
return "react";
if (lowerName.includes("typescript") || lowerName.includes("javascript"))
return "typescript";
if (lowerName.includes("node")) return "nodejs";
if (lowerName.includes("python")) return "python";
if (lowerName.includes("docker")) return "docker";
if (lowerName.includes("aws")) return "aws";
if (lowerName.includes("kubernetes")) return "kubernetes";
if (lowerName.includes("git")) return "default";
return "default";
};
return (
<div
className={`bg-white/5 border border-white/10 rounded-xl p-6 backdrop-blur-sm ${className}`}
@@ -103,7 +90,7 @@ export function MentorSection({
<div className="flex flex-wrap gap-3">
{masteredSkills.length > 0 ? (
masteredSkills.map((tech) => {
const colors = getTechColors(getTechColor(tech.name));
const colors = getImportanceColors(tech.importance);
return (
<div
key={tech.id}
@@ -138,7 +125,7 @@ export function MentorSection({
<div className="flex flex-wrap gap-3">
{mentorSkills.length > 0 ? (
mentorSkills.map((tech) => {
const colors = getTechColors(getTechColor(tech.name));
const colors = getImportanceColors(tech.importance);
return (
<div
key={tech.id}
@@ -172,7 +159,7 @@ export function MentorSection({
<div className="flex flex-wrap gap-3">
{learningSkills.length > 0 ? (
learningSkills.map((tech) => {
const colors = getTechColors(getTechColor(tech.name));
const colors = getImportanceColors(tech.importance);
return (
<div
key={tech.id}