This commit is contained in:
Julien Froidefond
2025-08-20 15:43:24 +02:00
commit 09d2c5cbe1
100 changed files with 12494 additions and 0 deletions

33
lib/data-loader.ts Normal file
View File

@@ -0,0 +1,33 @@
import { SkillCategory, Team } from "./types";
export async function loadSkillCategories(): Promise<SkillCategory[]> {
const categories = ["frontend", "backend", "devops", "mobile"];
const skillCategories: SkillCategory[] = [];
for (const category of categories) {
try {
const response = await fetch(`/data/skills/${category}.json`);
if (response.ok) {
const data = await response.json();
skillCategories.push(data);
}
} catch (error) {
console.error(`Failed to load ${category} skills:`, error);
}
}
return skillCategories;
}
export async function loadTeams(): Promise<Team[]> {
try {
const response = await fetch("/data/teams.json");
if (response.ok) {
const data = await response.json();
return data.teams;
}
} catch (error) {
console.error("Failed to load teams:", error);
}
return [];
}

76
lib/evaluation-utils.ts Normal file
View File

@@ -0,0 +1,76 @@
import {
SkillLevel,
SKILL_LEVEL_VALUES,
CategoryEvaluation,
RadarChartData,
UserEvaluation,
SkillCategory,
} from "./types";
export function calculateCategoryScore(
categoryEvaluation: CategoryEvaluation
): number {
if (categoryEvaluation.skills.length === 0) return 0;
const evaluatedSkills = categoryEvaluation.skills.filter(
(skill) => skill.level !== null
);
if (evaluatedSkills.length === 0) return 0;
const totalScore = evaluatedSkills.reduce((sum, skill) => {
return sum + SKILL_LEVEL_VALUES[skill.level!];
}, 0);
return totalScore / evaluatedSkills.length;
}
export function generateRadarData(
evaluations: CategoryEvaluation[],
categories: SkillCategory[]
): RadarChartData[] {
const maxScore = 3; // Expert level
return categories.map((category) => {
const evaluation = evaluations.find(
(e) => e.category === category.category
);
const score = evaluation ? calculateCategoryScore(evaluation) : 0;
return {
category: category.category,
score: Math.round(score * 10) / 10, // Round to 1 decimal
maxScore,
};
});
}
export function saveUserEvaluation(evaluation: UserEvaluation): void {
try {
localStorage.setItem(
"peakSkills_userEvaluation",
JSON.stringify(evaluation)
);
} catch (error) {
console.error("Failed to save user evaluation:", error);
}
}
export function loadUserEvaluation(): UserEvaluation | null {
try {
const saved = localStorage.getItem("peakSkills_userEvaluation");
return saved ? JSON.parse(saved) : null;
} catch (error) {
console.error("Failed to load user evaluation:", error);
return null;
}
}
export function createEmptyEvaluation(
categories: SkillCategory[]
): CategoryEvaluation[] {
return categories.map((category) => ({
category: category.category,
skills: [],
selectedSkillIds: [],
}));
}

44
lib/pattern-colors.ts Normal file
View File

@@ -0,0 +1,44 @@
export const getCategoryColor = (category: string) => {
switch (category) {
case "Creational":
return "border-blue-400 dark:border-blue-400 text-blue-600 dark:text-blue-400 bg-blue-50/50 dark:bg-blue-950/30 hover:bg-blue-100/70 dark:hover:bg-blue-900/50 hover:border-blue-500 dark:hover:border-blue-300 hover:shadow-blue-500/20";
case "Structural":
return "border-green-400 dark:border-green-400 text-green-600 dark:text-green-400 bg-green-50/50 dark:bg-green-950/30 hover:bg-green-100/70 dark:hover:bg-green-900/50 hover:border-green-500 dark:hover:border-green-300 hover:shadow-green-500/20";
case "Behavioral":
return "border-purple-400 dark:border-purple-400 text-purple-600 dark:text-purple-400 bg-purple-50/50 dark:bg-purple-950/30 hover:bg-purple-100/70 dark:hover:bg-purple-900/50 hover:border-purple-500 dark:hover:border-purple-300 hover:shadow-purple-500/20";
case "Architectural":
return "border-orange-400 dark:border-orange-400 text-orange-600 dark:text-orange-400 bg-orange-50/50 dark:bg-orange-950/30 hover:bg-orange-100/70 dark:hover:bg-orange-900/50 hover:border-orange-500 dark:hover:border-orange-300 hover:shadow-orange-500/20";
default:
return "border-gray-400 dark:border-gray-400 text-gray-600 dark:text-gray-400 bg-gray-50/50 dark:bg-gray-950/30 hover:bg-gray-100/70 dark:hover:bg-gray-900/50 hover:border-gray-500 dark:hover:border-gray-300 hover:shadow-gray-500/20";
}
};
export const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case "Facile":
return "border-emerald-400 dark:border-emerald-400 text-emerald-600 dark:text-emerald-400 bg-emerald-50/50 dark:bg-emerald-950/30 hover:bg-emerald-100/70 dark:hover:bg-emerald-900/50 hover:border-emerald-500 dark:hover:border-emerald-300 hover:shadow-emerald-500/20";
case "Moyen":
return "border-amber-400 dark:border-amber-400 text-amber-600 dark:text-amber-400 bg-amber-50/50 dark:bg-amber-950/30 hover:bg-amber-100/70 dark:hover:bg-amber-900/50 hover:border-amber-500 dark:hover:border-amber-300 hover:shadow-amber-500/20";
case "Difficile":
return "border-red-400 dark:border-red-400 text-red-600 dark:text-red-400 bg-red-50/50 dark:bg-red-950/30 hover:bg-red-100/70 dark:hover:bg-red-900/50 hover:border-red-500 dark:hover:border-red-300 hover:shadow-red-500/20";
default:
return "border-gray-400 dark:border-gray-400 text-gray-600 dark:text-gray-400 bg-gray-50/50 dark:bg-gray-950/30 hover:bg-gray-100/70 dark:hover:bg-gray-900/50 hover:border-gray-500 dark:hover:border-gray-300 hover:shadow-gray-500/20";
}
};
export const getUsageColor = (usage: string) => {
switch (usage) {
case "Peu utilisé":
return "border-red-400 dark:border-red-400 text-red-600 dark:text-red-400 bg-red-50/50 dark:bg-red-950/30 hover:bg-red-100/70 dark:hover:bg-red-900/50 hover:border-red-500 dark:hover:border-red-300 hover:shadow-red-500/20";
case "Modérément utilisé":
return "border-orange-400 dark:border-orange-400 text-orange-600 dark:text-orange-400 bg-orange-50/50 dark:bg-orange-950/30 hover:bg-orange-100/70 dark:hover:bg-orange-900/50 hover:border-orange-500 dark:hover:border-orange-300 hover:shadow-orange-500/20";
case "Utilisé":
return "border-amber-400 dark:border-amber-400 text-amber-600 dark:text-amber-400 bg-amber-50/50 dark:bg-amber-950/30 hover:bg-amber-100/70 dark:hover:bg-amber-900/50 hover:border-amber-500 dark:hover:border-amber-300 hover:shadow-amber-500/20";
case "Assez utilisé":
return "border-lime-400 dark:border-lime-400 text-lime-600 dark:text-lime-400 bg-lime-50/50 dark:bg-lime-950/30 hover:bg-lime-100/70 dark:hover:bg-lime-900/50 hover:border-lime-500 dark:hover:border-lime-300 hover:shadow-lime-500/20";
case "Très utilisé":
return "border-green-400 dark:border-green-400 text-green-600 dark:text-green-400 bg-green-50/50 dark:bg-green-950/30 hover:bg-green-100/70 dark:hover:bg-green-900/50 hover:border-green-500 dark:hover:border-green-300 hover:shadow-green-500/20";
default:
return "border-gray-400 dark:border-gray-400 text-gray-600 dark:text-gray-400 bg-gray-50/50 dark:bg-gray-950/30 hover:bg-gray-100/70 dark:hover:bg-gray-900/50 hover:border-gray-500 dark:hover:border-gray-300 hover:shadow-gray-500/20";
}
};

61
lib/tech-colors.ts Normal file
View File

@@ -0,0 +1,61 @@
// Couleurs officielle des technologies
export const TECH_COLORS: Record<
string,
{ bg: string; text: string; border: string }
> = {
react: {
bg: "bg-blue-500/10",
text: "text-blue-600 dark:text-blue-400",
border: "border-blue-500/20",
},
vue: {
bg: "bg-green-500/10",
text: "text-green-600 dark:text-green-400",
border: "border-green-500/20",
},
typescript: {
bg: "bg-blue-500/10",
text: "text-blue-600 dark:text-blue-400",
border: "border-blue-500/20",
},
nextjs: {
bg: "bg-gray-500/10",
text: "text-gray-900 dark:text-gray-100",
border: "border-gray-500/20",
},
nodejs: {
bg: "bg-green-500/10",
text: "text-green-600 dark:text-green-400",
border: "border-green-500/20",
},
python: {
bg: "bg-yellow-500/10",
text: "text-yellow-600 dark:text-yellow-400",
border: "border-yellow-500/20",
},
docker: {
bg: "bg-blue-500/10",
text: "text-blue-600 dark:text-blue-400",
border: "border-blue-500/20",
},
kubernetes: {
bg: "bg-indigo-500/10",
text: "text-indigo-600 dark:text-indigo-400",
border: "border-indigo-500/20",
},
aws: {
bg: "bg-orange-500/10",
text: "text-orange-600 dark:text-orange-400",
border: "border-orange-500/20",
},
// Couleur par défaut
default: {
bg: "bg-muted",
text: "text-foreground",
border: "border-muted",
},
};
export function getTechColors(techId: string) {
return TECH_COLORS[techId] || TECH_COLORS.default;
}

67
lib/types.ts Normal file
View File

@@ -0,0 +1,67 @@
export type SkillLevel =
| "never"
| "not-autonomous"
| "autonomous"
| "expert"
| null;
export const SKILL_LEVELS: Record<Exclude<SkillLevel, null>, string> = {
never: "Jamais pratiqué",
"not-autonomous": "Pas autonome",
autonomous: "Autonome",
expert: "Maîtrise",
};
export const SKILL_LEVEL_VALUES: Record<Exclude<SkillLevel, null>, number> = {
never: 0,
"not-autonomous": 1,
autonomous: 2,
expert: 3,
};
export interface Skill {
id: string;
name: string;
description: string;
links: string[];
}
export interface SkillCategory {
category: string;
skills: Skill[];
}
export interface Team {
id: string;
name: string;
direction: string;
}
export interface UserProfile {
firstName: string;
lastName: string;
teamId: string;
}
export interface SkillEvaluation {
skillId: string;
level: SkillLevel;
}
export interface CategoryEvaluation {
category: string;
skills: SkillEvaluation[];
selectedSkillIds: string[]; // List of skill IDs the user wants to evaluate
}
export interface UserEvaluation {
profile: UserProfile;
evaluations: CategoryEvaluation[];
lastUpdated: string;
}
export interface RadarChartData {
category: string;
score: number;
maxScore: number;
}

6
lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}