From ab9c35c276463dd45fa1d47c0a1542ad8b3a7ab2 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 20 Aug 2025 16:50:30 +0200 Subject: [PATCH] Add score color logic and evaluation migration - Introduced `getScoreColors` function to dynamically set badge colors based on skill scores for better visual feedback. - Updated HomePage to display skill evaluation percentages with corresponding colors. - Implemented `migrateEvaluation` function to ensure existing evaluations are updated with new skill categories, enhancing data integrity. - Refactored data loading in `loadSkillCategories` and `loadTeams` to fetch from API endpoints, improving flexibility and maintainability. --- app/api/skills/route.ts | 41 +++++++++++++++++ app/api/teams/route.ts | 25 +++++++++++ app/page.tsx | 98 ++++++++++++++++++++++++++++++++++------- hooks/use-evaluation.ts | 39 +++++++++++++++- lib/data-loader.ts | 44 +++++++++--------- 5 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 app/api/skills/route.ts create mode 100644 app/api/teams/route.ts diff --git a/app/api/skills/route.ts b/app/api/skills/route.ts new file mode 100644 index 0000000..413b75f --- /dev/null +++ b/app/api/skills/route.ts @@ -0,0 +1,41 @@ +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { SkillCategory } from "@/lib/types"; + +export async function GET() { + try { + const dataDir = path.join(process.cwd(), "data", "skills"); + + const categories = [ + "frontend", + "backend", + "devops", + "mobile", + "data", + "cloud", + "security", + "design", + ]; + + const skillCategories: SkillCategory[] = []; + + for (const category of categories) { + const filePath = path.join(dataDir, `${category}.json`); + + if (fs.existsSync(filePath)) { + const fileContent = fs.readFileSync(filePath, "utf-8"); + const data = JSON.parse(fileContent); + skillCategories.push(data); + } + } + + return NextResponse.json(skillCategories); + } catch (error) { + console.error("Error loading skills:", error); + return NextResponse.json( + { error: "Failed to load skills" }, + { status: 500 } + ); + } +} diff --git a/app/api/teams/route.ts b/app/api/teams/route.ts new file mode 100644 index 0000000..f4a0730 --- /dev/null +++ b/app/api/teams/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { Team } from "@/lib/types"; + +export async function GET() { + try { + const filePath = path.join(process.cwd(), "data", "teams.json"); + + if (!fs.existsSync(filePath)) { + return NextResponse.json({ teams: [] }); + } + + const fileContent = fs.readFileSync(filePath, "utf-8"); + const data = JSON.parse(fileContent); + + return NextResponse.json(data.teams as Team[]); + } catch (error) { + console.error("Error loading teams:", error); + return NextResponse.json( + { error: "Failed to load teams" }, + { status: 500 } + ); + } +} diff --git a/app/page.tsx b/app/page.tsx index cddd84f..1bbec66 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -20,6 +20,43 @@ import Link from "next/link"; import { Code2, ChevronDown, ChevronRight, ExternalLink } from "lucide-react"; import { getCategoryIcon } from "@/lib/category-icons"; +// Fonction pour déterminer la couleur du badge selon le niveau moyen +function getScoreColors(score: number) { + if (score >= 2.5) { + // Expert/Maîtrise (violet) + return { + bg: "bg-violet-500/20", + border: "border-violet-500/30", + text: "text-violet-400", + gradient: "from-violet-500 to-violet-400", + }; + } else if (score >= 1.5) { + // Autonome (vert) + return { + bg: "bg-green-500/20", + border: "border-green-500/30", + text: "text-green-400", + gradient: "from-green-500 to-green-400", + }; + } else if (score >= 0.5) { + // Non autonome (orange/amber) + return { + bg: "bg-amber-500/20", + border: "border-amber-500/30", + text: "text-amber-400", + gradient: "from-amber-500 to-amber-400", + }; + } else { + // Jamais pratiqué (rouge) + return { + bg: "bg-red-500/20", + border: "border-red-500/30", + text: "text-red-400", + gradient: "from-red-500 to-red-400", + }; + } +} + export default function HomePage() { const { userEvaluation, skillCategories, teams, loading, updateProfile } = useEvaluation(); @@ -186,26 +223,53 @@ export default function HomePage() { {category.category} -
- - {category.score.toFixed(1)}/3 - -
+ {skillsCount > 0 ? ( + (() => { + const colors = getScoreColors(category.score); + return ( +
+ + {Math.round((category.score / 3) * 100)}% + +
+ ); + })() + ) : ( +
+ + Aucune + +
+ )}
- {evaluatedCount}/{skillsCount} compétences sélectionnées - évaluées -
-
-
+ {skillsCount > 0 + ? `${evaluatedCount}/${skillsCount} compétences sélectionnées évaluées` + : "Aucune compétence sélectionnée"}
+ {skillsCount > 0 ? ( +
+ {(() => { + const colors = getScoreColors(category.score); + return ( +
+ ); + })()} +
+ ) : ( +
+ )} {/* Expanded Skills */} diff --git a/hooks/use-evaluation.ts b/hooks/use-evaluation.ts index 9730718..1c5d8b9 100644 --- a/hooks/use-evaluation.ts +++ b/hooks/use-evaluation.ts @@ -16,6 +16,40 @@ import { } from "@/lib/evaluation-utils"; import { loadSkillCategories, loadTeams } from "@/lib/data-loader"; +// Fonction pour migrer une évaluation existante avec de nouvelles catégories +function migrateEvaluation( + evaluation: UserEvaluation, + allCategories: SkillCategory[] +): UserEvaluation { + const existingCategoryNames = evaluation.evaluations.map((e) => e.category); + const missingCategories = allCategories.filter( + (cat) => !existingCategoryNames.includes(cat.category) + ); + + if (missingCategories.length === 0) { + return evaluation; // Pas de migration nécessaire + } + + console.log( + "🔄 Migrating evaluation with new categories:", + missingCategories.map((c) => c.category) + ); + + const newCategoryEvaluations: CategoryEvaluation[] = missingCategories.map( + (category) => ({ + category: category.category, + skills: [], + selectedSkillIds: [], + }) + ); + + return { + ...evaluation, + evaluations: [...evaluation.evaluations, ...newCategoryEvaluations], + lastUpdated: new Date().toISOString(), + }; +} + export function useEvaluation() { const [userEvaluation, setUserEvaluation] = useState( null @@ -39,7 +73,10 @@ export function useEvaluation() { // Try to load existing evaluation const saved = loadUserEvaluation(); if (saved) { - setUserEvaluation(saved); + // Migrate evaluation to include new categories if needed + const migratedEvaluation = migrateEvaluation(saved, categories); + setUserEvaluation(migratedEvaluation); + saveUserEvaluation(migratedEvaluation); // Save the migrated version } } catch (error) { console.error("Failed to initialize data:", error); diff --git a/lib/data-loader.ts b/lib/data-loader.ts index f48ee96..b833bb9 100644 --- a/lib/data-loader.ts +++ b/lib/data-loader.ts @@ -1,29 +1,27 @@ import { SkillCategory, Team } from "./types"; -// Import direct des données JSON depuis le dossier /data -import frontendData from "@/data/skills/frontend.json"; -import backendData from "@/data/skills/backend.json"; -import devopsData from "@/data/skills/devops.json"; -import mobileData from "@/data/skills/mobile.json"; -import dataData from "@/data/skills/data.json"; -import cloudData from "@/data/skills/cloud.json"; -import securityData from "@/data/skills/security.json"; -import designData from "@/data/skills/design.json"; -import teamsData from "@/data/teams.json"; - export async function loadSkillCategories(): Promise { - return [ - frontendData, - backendData, - devopsData, - mobileData, - dataData, - cloudData, - securityData, - designData, - ] as SkillCategory[]; + try { + const response = await fetch("/api/skills"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Failed to load skill categories:", error); + return []; + } } export async function loadTeams(): Promise { - return teamsData.teams as Team[]; -} \ No newline at end of file + try { + const response = await fetch("/api/teams"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Failed to load teams:", error); + return []; + } +}