feat: review admin teams views for importance inclusion

This commit is contained in:
Julien Froidefond
2025-08-27 13:43:47 +02:00
parent e9aecca2a5
commit a5bcdd34fb
10 changed files with 1041 additions and 409 deletions

View File

@@ -5,6 +5,7 @@ import { TrendingUp, MessageSquare, Lightbulb } from "lucide-react";
interface SkillAnalysis {
skillName: string;
category: string;
importance: "incontournable" | "majeure" | "standard";
experts: Array<{
name: string;
level: number;
@@ -19,14 +20,27 @@ interface SkillAnalysis {
expertCount: number;
learnerCount: number;
proficiencyRate: number;
coverage: number;
}
interface TeamInsights {
averageTeamLevel: number;
totalExperts: number;
totalLearners: number;
skillGaps: number;
strongSkills: number;
skillGaps: {
incontournable: number;
majeure: number;
standard: number;
};
strongSkills: {
incontournable: number;
majeure: number;
standard: number;
};
criticalSkillsCoverage: {
incontournable: number;
majeure: number;
};
}
interface TeamInsightsTabProps {
@@ -41,35 +55,68 @@ export function TeamInsightsTab({
return (
<>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Compétences à développer */}
{/* Compétences critiques à développer */}
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-6">
<h3 className="text-lg font-semibold text-white mb-6 flex items-center gap-2">
<TrendingUp className="h-5 w-5 text-red-400" />
Compétences à développer
Compétences critiques à développer
</h3>
<div className="space-y-3">
<div className="space-y-1">
{/* Incontournables */}
{skillAnalysis
.filter((s) => s.averageLevel < 1.5)
.slice(0, 5)
.filter(
(s) => s.importance === "incontournable" && s.coverage < 75
)
.map((skill, idx) => (
<div
key={idx}
className="flex items-center justify-between p-3 bg-white/5 rounded-lg"
className="flex items-center gap-2 py-1.5 px-2 bg-red-500/5 hover:bg-red-500/10 border-l-2 border-red-500 rounded-sm group"
>
<div>
<div className="text-white font-medium">
{skill.skillName}
<div className="flex-1 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="text-sm text-slate-200">
{skill.skillName}
</div>
<div className="text-[10px] text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity">
{skill.category}
</div>
</div>
<div className="text-xs text-slate-400">
{skill.category}
<div className="flex items-center gap-2">
<div className="text-[10px] text-red-300 opacity-0 group-hover:opacity-100 transition-opacity">
Objectif: 75%
</div>
<div className="text-xs text-red-400 font-medium">
{skill.coverage.toFixed(0)}%
</div>
</div>
</div>
<div className="text-right">
<div className="text-red-300 font-semibold">
{skill.averageLevel.toFixed(1)}
</div>
))}
{/* Majeures */}
{skillAnalysis
.filter((s) => s.importance === "majeure" && s.coverage < 60)
.map((skill, idx) => (
<div
key={idx}
className="flex items-center gap-2 py-1.5 px-2 bg-blue-500/5 hover:bg-blue-500/10 border-l-2 border-blue-500 rounded-sm group"
>
<div className="flex-1 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="text-sm text-slate-200">
{skill.skillName}
</div>
<div className="text-[10px] text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity">
{skill.category}
</div>
</div>
<div className="text-xs text-slate-400">
{skill.learnerCount} intéressés
<div className="flex items-center gap-2">
<div className="text-[10px] text-blue-300 opacity-0 group-hover:opacity-100 transition-opacity">
Objectif: 60%
</div>
<div className="text-xs text-blue-400 font-medium">
{skill.coverage.toFixed(0)}%
</div>
</div>
</div>
</div>
@@ -83,34 +130,66 @@ export function TeamInsightsTab({
<MessageSquare className="h-5 w-5 text-green-400" />
Opportunités de mentorat
</h3>
<div className="space-y-3">
<div className="space-y-1">
{skillAnalysis
.filter(
(s) =>
s.experts.filter((e) => e.canMentor).length > 0 &&
s.learnerCount > 0
s.learnerCount > 0 &&
(s.importance === "incontournable" ||
s.importance === "majeure")
)
.slice(0, 5)
.sort((a, b) => {
// D'abord par importance
const importanceOrder = {
incontournable: 2,
majeure: 1,
standard: 0,
};
const importanceDiff =
importanceOrder[b.importance] - importanceOrder[a.importance];
if (importanceDiff !== 0) return importanceDiff;
// Ensuite par nombre d'apprenants
return b.learnerCount - a.learnerCount;
})
.map((skill, idx) => (
<div key={idx} className="p-3 bg-white/5 rounded-lg">
<div className="flex items-center justify-between mb-2">
<div className="text-white font-medium">
{skill.skillName}
<div
key={idx}
className={`flex items-center gap-2 py-1.5 px-2 rounded-sm group ${
skill.importance === "incontournable"
? "bg-red-500/5 hover:bg-red-500/10 border-l-2 border-red-500"
: "bg-blue-500/5 hover:bg-blue-500/10 border-l-2 border-blue-500"
}`}
>
<div className="flex-1 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="text-sm text-slate-200">
{skill.skillName}
</div>
<div
className={`text-[10px] ${
skill.importance === "incontournable"
? "text-red-400"
: "text-blue-400"
} opacity-0 group-hover:opacity-100 transition-opacity`}
>
{skill.importance === "incontournable"
? "Incontournable"
: "Majeure"}
</div>
</div>
<div className="text-xs text-slate-400">
{skill.category}
</div>
</div>
<div className="flex items-center justify-between text-xs">
<div className="text-green-300">
{skill.experts.filter((e) => e.canMentor).length} mentor
{skill.experts.filter((e) => e.canMentor).length > 1
? "s"
: ""}
</div>
<div className="text-blue-300">
{skill.learnerCount} apprenant
{skill.learnerCount > 1 ? "s" : ""}
<div className="flex items-center gap-3 text-xs">
<div className="text-green-300">
{skill.experts.filter((e) => e.canMentor).length} mentor
{skill.experts.filter((e) => e.canMentor).length > 1
? "s"
: ""}
</div>
<div className="text-blue-300">
{skill.learnerCount} apprenant
{skill.learnerCount > 1 ? "s" : ""}
</div>
</div>
</div>
</div>
@@ -126,58 +205,104 @@ export function TeamInsightsTab({
Recommandations pour l'équipe
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-blue-500/10 border border-blue-500/20 rounded-xl">
<h4 className="font-medium text-blue-300 mb-2">
🎯 Formations prioritaires
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl">
<h4 className="font-medium text-red-300 mb-2">
🎯 Compétences incontournables
</h4>
<p className="text-sm text-slate-300">
Organiser des formations sur{" "}
{skillAnalysis
.filter((s) => s.averageLevel < 1.5)
.slice(0, 2)
.map((s) => s.skillName)
.join(" et ")}
pour combler les lacunes identifiées.
{teamInsights.skillGaps.incontournable > 0 ? (
<>
Priorité: combler les lacunes sur{" "}
{teamInsights.skillGaps.incontournable} compétence
{teamInsights.skillGaps.incontournable > 1 ? "s" : ""}{" "}
incontournable
{teamInsights.skillGaps.incontournable > 1 ? "s" : ""} sous
l'objectif de 75%.
</>
) : (
<>
Excellent ! Toutes les compétences incontournables atteignent
leur objectif de couverture.
</>
)}
</p>
</div>
<div className="p-4 bg-blue-500/10 border border-blue-500/20 rounded-xl">
<h4 className="font-medium text-blue-300 mb-2">
🎓 Compétences majeures
</h4>
<p className="text-sm text-slate-300">
{teamInsights.skillGaps.majeure > 0 ? (
<>
Attention: {teamInsights.skillGaps.majeure} compétence
{teamInsights.skillGaps.majeure > 1 ? "s" : ""} majeure
{teamInsights.skillGaps.majeure > 1 ? "s" : ""} n'atteigne
{teamInsights.skillGaps.majeure > 1 ? "nt" : ""} pas
l'objectif de 60%.
</>
) : (
<>
Bravo ! Toutes les compétences majeures atteignent leur
objectif de couverture.
</>
)}
</p>
</div>
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-xl">
<h4 className="font-medium text-green-300 mb-2">
🤝 Programme de mentorat
🤝 Opportunités de mentorat
</h4>
<p className="text-sm text-slate-300">
Mettre en place un système de mentorat avec{" "}
{skillAnalysis.reduce(
(sum, skill) =>
sum + skill.experts.filter((e) => e.canMentor).length,
0
)}{" "}
mentors disponibles.
</p>
</div>
<div className="p-4 bg-purple-500/10 border border-purple-500/20 rounded-xl">
<h4 className="font-medium text-purple-300 mb-2">
📈 Capitaliser sur les forces
</h4>
<p className="text-sm text-slate-300">
Exploiter l'expertise en{" "}
{skillAnalysis
.filter((s) => s.averageLevel >= 2.5)
.slice(0, 2)
.map((s) => s.skillName)
.join(" et ")}
pour des projets ambitieux.
{skillAnalysis.filter(
(s) =>
s.experts.filter((e) => e.canMentor).length > 0 &&
s.learnerCount > 0 &&
(s.importance === "incontournable" ||
s.importance === "majeure")
).length > 0 ? (
<>
{
skillAnalysis.filter(
(s) =>
s.experts.filter((e) => e.canMentor).length > 0 &&
s.learnerCount > 0 &&
(s.importance === "incontournable" ||
s.importance === "majeure")
).length
}{" "}
opportunité
{skillAnalysis.filter(
(s) =>
s.experts.filter((e) => e.canMentor).length > 0 &&
s.learnerCount > 0 &&
(s.importance === "incontournable" ||
s.importance === "majeure")
).length > 1
? "s"
: ""}{" "}
de mentorat sur des compétences critiques.
</>
) : (
<>
Aucune opportunité de mentorat identifiée sur les compétences
critiques.
</>
)}
</p>
</div>
<div className="p-4 bg-orange-500/10 border border-orange-500/20 rounded-xl">
<h4 className="font-medium text-orange-300 mb-2">
🔄 Plan de développement
📊 Couverture globale
</h4>
<p className="text-sm text-slate-300">
Créer des parcours de montée en compétences personnalisés pour{" "}
{teamInsights.totalLearners} objectifs d'apprentissage.
Incontournables:{" "}
{teamInsights.criticalSkillsCoverage.incontournable.toFixed(0)}%
{" • "}
Majeures: {teamInsights.criticalSkillsCoverage.majeure.toFixed(0)}
%
</p>
</div>
</div>