feat: review my team page with importance
This commit is contained in:
@@ -13,7 +13,13 @@ import {
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import {
|
||||
Tooltip as UITooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface TeamStatsProps {
|
||||
stats: TeamReviewData["stats"];
|
||||
@@ -73,20 +79,45 @@ export function TeamStats({
|
||||
const learnerCount = members.filter((m) => m.learningSkills > 0).length;
|
||||
const mentorshipRatio = (mentorCount / (stats.totalMembers || 1)) * 100;
|
||||
|
||||
// Gaps critiques par catégorie
|
||||
const criticalGapsByCategory = skillGaps
|
||||
.filter((gap) => gap.risk === "high")
|
||||
.reduce((acc, gap) => {
|
||||
acc[gap.category] = (acc[gap.category] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
// Gaps critiques par catégorie, séparés par importance
|
||||
const criticalGapsByCategory = skillGaps.reduce((acc, gap) => {
|
||||
const isIncontournableUndercovered =
|
||||
gap.importance === "incontournable" && gap.coverage < 75;
|
||||
const isMajeureUndercovered =
|
||||
gap.importance === "majeure" && gap.coverage < 60;
|
||||
|
||||
const gapData = Object.entries(criticalGapsByCategory).map(
|
||||
([category, count]) => ({
|
||||
if (isIncontournableUndercovered || isMajeureUndercovered) {
|
||||
if (!acc[gap.category]) {
|
||||
acc[gap.category] = { incontournable: 0, majeure: 0 };
|
||||
}
|
||||
if (isIncontournableUndercovered) {
|
||||
acc[gap.category].incontournable++;
|
||||
}
|
||||
if (isMajeureUndercovered) {
|
||||
acc[gap.category].majeure++;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, { incontournable: number; majeure: number }>);
|
||||
|
||||
const gapData = Object.entries(criticalGapsByCategory)
|
||||
.map(([category, counts]) => ({
|
||||
name: category,
|
||||
value: count,
|
||||
fill: "#ef4444",
|
||||
})
|
||||
incontournable: counts.incontournable,
|
||||
majeure: counts.majeure,
|
||||
}))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
// Trier d'abord par nombre total de compétences critiques
|
||||
b.incontournable + b.majeure - (a.incontournable + a.majeure) ||
|
||||
// Puis par nombre de compétences incontournables
|
||||
b.incontournable - a.incontournable
|
||||
);
|
||||
|
||||
// Nombre total de compétences critiques sous-couvertes
|
||||
const totalCriticalGaps = Object.values(criticalGapsByCategory).reduce(
|
||||
(sum, counts) => sum + counts.incontournable + counts.majeure,
|
||||
0
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -117,11 +148,96 @@ export function TeamStats({
|
||||
{mentorshipRatio.toFixed(0)}%
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-slate-400">Gaps critiques</p>
|
||||
<p className="text-2xl font-bold text-red-400">
|
||||
{stats.learningNeeds}
|
||||
</p>
|
||||
<UITooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-slate-400">Compétences critiques</p>
|
||||
<p className="text-2xl font-bold text-red-400">
|
||||
{totalCriticalGaps}
|
||||
</p>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-900 text-slate-200 border border-slate-700">
|
||||
<div className="text-xs space-y-1">
|
||||
<p>Compétences critiques sous-couvertes :</p>
|
||||
<ul className="list-disc list-inside space-y-0.5">
|
||||
<li>Incontournables : couverture < 75%</li>
|
||||
<li>Majeures : couverture < 60%</li>
|
||||
</ul>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</UITooltip>
|
||||
</div>
|
||||
|
||||
{/* Couverture des compétences critiques */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-slate-200 mb-4">
|
||||
Couverture des compétences critiques
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<UITooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-slate-400">
|
||||
Incontournables
|
||||
</span>
|
||||
<span className="text-sm font-medium text-slate-200">
|
||||
{stats.criticalSkillsCoverage.incontournable.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-white/10 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-red-500/50 rounded-full transition-all"
|
||||
style={{
|
||||
width: `${Math.min(
|
||||
100,
|
||||
Math.max(
|
||||
0,
|
||||
stats.criticalSkillsCoverage.incontournable
|
||||
)
|
||||
)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-900 text-slate-200 border border-slate-700">
|
||||
<div className="text-xs">
|
||||
<p>Compétences incontournables</p>
|
||||
<p className="text-slate-400">Objectif : 75% de couverture</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</UITooltip>
|
||||
<UITooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-slate-400">Majeures</span>
|
||||
<span className="text-sm font-medium text-slate-200">
|
||||
{stats.criticalSkillsCoverage.majeure.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-white/10 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-orange-500/50 rounded-full transition-all"
|
||||
style={{
|
||||
width: `${Math.min(
|
||||
100,
|
||||
Math.max(0, stats.criticalSkillsCoverage.majeure)
|
||||
)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-900 text-slate-200 border border-slate-700">
|
||||
<div className="text-xs">
|
||||
<p>Compétences majeures</p>
|
||||
<p className="text-slate-400">Objectif : 60% de couverture</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</UITooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -198,9 +314,9 @@ export function TeamStats({
|
||||
{gapData.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-slate-200 mb-4">
|
||||
Gaps critiques par catégorie
|
||||
Compétences critiques sous-couvertes par catégorie
|
||||
</h3>
|
||||
<div className="h-[200px]">
|
||||
<div className="h-[250px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={gapData}>
|
||||
<CartesianGrid
|
||||
@@ -224,8 +340,36 @@ export function TeamStats({
|
||||
}}
|
||||
itemStyle={{ color: "#e2e8f0" }}
|
||||
labelStyle={{ color: "#e2e8f0" }}
|
||||
formatter={(value, name) => {
|
||||
const label =
|
||||
name === "incontournable"
|
||||
? "Incontournables (obj. 75%)"
|
||||
: "Majeures (obj. 60%)";
|
||||
return [value, label];
|
||||
}}
|
||||
/>
|
||||
<Legend
|
||||
formatter={(value) => {
|
||||
return value === "incontournable"
|
||||
? "Incontournables (obj. 75%)"
|
||||
: "Majeures (obj. 60%)";
|
||||
}}
|
||||
wrapperStyle={{
|
||||
paddingTop: "20px",
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="incontournable"
|
||||
fill="#ef4444"
|
||||
name="incontournable"
|
||||
stackId="stack"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="majeure"
|
||||
fill="#f97316"
|
||||
name="majeure"
|
||||
stackId="stack"
|
||||
/>
|
||||
<Bar dataKey="value" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user