feat: review my team page with importance
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { TeamReviewData } from "@/lib/team-review-types";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { getImportanceColors } from "@/lib/tech-colors";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface TeamOverviewProps {
|
||||
team: TeamReviewData["team"];
|
||||
stats: TeamReviewData["stats"];
|
||||
members: TeamReviewData["members"];
|
||||
categoryCoverage: TeamReviewData["categoryCoverage"];
|
||||
skillGaps: TeamReviewData["skillGaps"];
|
||||
}
|
||||
|
||||
export function TeamOverview({
|
||||
@@ -16,6 +25,7 @@ export function TeamOverview({
|
||||
stats,
|
||||
members,
|
||||
categoryCoverage,
|
||||
skillGaps,
|
||||
}: TeamOverviewProps) {
|
||||
// Trouver les top contributeurs
|
||||
const topContributors = [...members]
|
||||
@@ -29,24 +39,51 @@ export function TeamOverview({
|
||||
|
||||
// Trouver les catégories qui nécessitent de l'attention
|
||||
const categoriesNeedingAttention = [...categoryCoverage]
|
||||
.filter((cat) => {
|
||||
// Une catégorie nécessite de l'attention si :
|
||||
// - Couverture faible (< 40%)
|
||||
// - OU pas assez d'experts (< 2) avec une équipe de taille significative (> 5)
|
||||
// - OU beaucoup d'apprenants (> 30% de l'équipe) avec peu de mentors (< 2)
|
||||
return (
|
||||
cat.coverage < 40 ||
|
||||
(cat.experts < 2 && stats.totalMembers > 5) ||
|
||||
(cat.learners > stats.totalMembers * 0.3 && cat.mentors < 2)
|
||||
.map((cat) => {
|
||||
// Pour chaque catégorie, on identifie :
|
||||
// 1. Les compétences incontournables sous-couvertes (< 75%)
|
||||
const uncoveredIncontournables = skillGaps.filter(
|
||||
(gap) =>
|
||||
gap.category === cat.category &&
|
||||
gap.importance === "incontournable" &&
|
||||
gap.coverage < 75
|
||||
);
|
||||
|
||||
// 2. Les compétences majeures sous-couvertes (< 60%)
|
||||
const uncoveredMajeures = skillGaps.filter(
|
||||
(gap) =>
|
||||
gap.category === cat.category &&
|
||||
gap.importance === "majeure" &&
|
||||
gap.coverage < 60
|
||||
);
|
||||
|
||||
// Une catégorie nécessite de l'attention si :
|
||||
const needsAttention =
|
||||
uncoveredIncontournables.length > 0 || // Il y a des compétences incontournables sous-couvertes
|
||||
uncoveredMajeures.length > 0 || // OU des compétences majeures sous-couvertes
|
||||
(cat.experts < 2 && stats.totalMembers > 5); // OU pas assez d'experts dans une équipe significative
|
||||
|
||||
if (!needsAttention) return null;
|
||||
|
||||
// Calculer un score d'attention pour trier les catégories
|
||||
const attentionScore =
|
||||
// Les incontournables pèsent plus lourd
|
||||
uncoveredIncontournables.length * 3 +
|
||||
// Les majeures un peu moins
|
||||
uncoveredMajeures.length * 2 +
|
||||
// Manque d'experts est un facteur aggravant
|
||||
(cat.experts < 2 && stats.totalMembers > 5 ? 1 : 0);
|
||||
|
||||
return {
|
||||
...cat,
|
||||
// On combine les deux types pour l'affichage
|
||||
criticalSkills: [...uncoveredIncontournables, ...uncoveredMajeures],
|
||||
attentionScore,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Prioriser les catégories avec le plus de besoins
|
||||
const aScore = a.learners * 2 - a.mentors - a.experts;
|
||||
const bScore = b.learners * 2 - b.mentors - b.experts;
|
||||
return bScore - aScore;
|
||||
})
|
||||
.slice(0, 2);
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => b.attentionScore - a.attentionScore)
|
||||
.slice(0, 3);
|
||||
|
||||
return (
|
||||
<Card className="bg-white/5 border-white/10 backdrop-blur">
|
||||
@@ -130,23 +167,76 @@ export function TeamOverview({
|
||||
variant="outline"
|
||||
className="text-amber-400 border-amber-400/30"
|
||||
>
|
||||
{cat.learners} apprenants
|
||||
{cat.experts} exp • {cat.mentors} ment
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-slate-400">
|
||||
<span>{cat.experts} experts</span>
|
||||
<span>{cat.mentors} mentors</span>
|
||||
</div>
|
||||
<div className="text-xs text-amber-400/80">
|
||||
{cat.coverage < 40 && "Couverture insuffisante • "}
|
||||
{cat.experts < 2 &&
|
||||
stats.totalMembers > 5 &&
|
||||
"Manque d'experts • "}
|
||||
{cat.learners > stats.totalMembers * 0.3 &&
|
||||
cat.mentors < 2 &&
|
||||
"Besoin de mentors"}
|
||||
</div>
|
||||
{/* Compétences sous-couvertes */}
|
||||
{cat.criticalSkills.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{cat.criticalSkills
|
||||
.sort((a, b) => {
|
||||
if (
|
||||
a.importance === "incontournable" &&
|
||||
b.importance !== "incontournable"
|
||||
)
|
||||
return -1;
|
||||
if (
|
||||
a.importance !== "incontournable" &&
|
||||
b.importance === "incontournable"
|
||||
)
|
||||
return 1;
|
||||
return a.coverage - b.coverage;
|
||||
})
|
||||
.map((skill) => {
|
||||
const colors = getImportanceColors(skill.importance);
|
||||
const target =
|
||||
skill.importance === "incontournable" ? 75 : 60;
|
||||
return (
|
||||
<Tooltip key={skill.skillId}>
|
||||
<TooltipTrigger>
|
||||
<div
|
||||
className={`text-xs rounded-md px-1.5 py-0.5 ${colors.bg} ${colors.border} border flex items-center gap-1.5`}
|
||||
>
|
||||
<span className={colors.text}>
|
||||
{skill.skillName}
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
skill.coverage < target
|
||||
? "text-red-400"
|
||||
: "text-slate-400"
|
||||
}
|
||||
>
|
||||
{skill.coverage.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-slate-900 text-slate-200 border border-slate-700">
|
||||
<div className="text-xs">
|
||||
<p className="font-medium">
|
||||
{skill.importance === "incontournable"
|
||||
? "Compétence incontournable"
|
||||
: "Compétence majeure"}
|
||||
</p>
|
||||
<p className="text-slate-400">
|
||||
Objectif : {target}% de couverture
|
||||
<br />
|
||||
Actuel : {skill.coverage.toFixed(0)}%
|
||||
<br />
|
||||
{skill.coverage < target
|
||||
? `Manque ${(
|
||||
target - skill.coverage
|
||||
).toFixed(0)}%`
|
||||
: "Objectif atteint"}
|
||||
</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{stats.learningNeeds > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user