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.
This commit is contained in:
41
app/api/skills/route.ts
Normal file
41
app/api/skills/route.ts
Normal file
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
25
app/api/teams/route.ts
Normal file
25
app/api/teams/route.ts
Normal file
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
76
app/page.tsx
76
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}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="px-2 py-0.5 rounded-full bg-blue-500/20 border border-blue-500/30">
|
||||
<span className="text-xs font-medium text-blue-400">
|
||||
{category.score.toFixed(1)}/3
|
||||
{skillsCount > 0 ? (
|
||||
(() => {
|
||||
const colors = getScoreColors(category.score);
|
||||
return (
|
||||
<div
|
||||
className={`px-2 py-0.5 rounded-full ${colors.bg} border ${colors.border}`}
|
||||
>
|
||||
<span
|
||||
className={`text-xs font-medium ${colors.text}`}
|
||||
>
|
||||
{Math.round((category.score / 3) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<div className="px-2 py-0.5 rounded-full bg-slate-500/20 border border-slate-500/30">
|
||||
<span className="text-xs font-medium text-slate-400">
|
||||
Aucune
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-slate-400 mb-2">
|
||||
{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"}
|
||||
</div>
|
||||
{skillsCount > 0 ? (
|
||||
<div className="w-full bg-white/10 rounded-full h-1.5">
|
||||
{(() => {
|
||||
const colors = getScoreColors(category.score);
|
||||
return (
|
||||
<div
|
||||
className="bg-gradient-to-r from-blue-500 to-blue-400 h-1.5 rounded-full transition-all"
|
||||
className={`bg-gradient-to-r ${colors.gradient} h-1.5 rounded-full transition-all`}
|
||||
style={{
|
||||
width: `${
|
||||
(category.score / category.maxScore) * 100
|
||||
}%`,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full bg-slate-500/10 rounded-full h-1.5"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Expanded Skills */}
|
||||
|
||||
@@ -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<UserEvaluation | null>(
|
||||
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);
|
||||
|
||||
@@ -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<SkillCategory[]> {
|
||||
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<Team[]> {
|
||||
return teamsData.teams as Team[];
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user