feat: my team page

This commit is contained in:
Julien Froidefond
2025-08-27 10:53:11 +02:00
parent b7e6fa257e
commit c7a5b25501
11 changed files with 1529 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ import {
Settings,
Building2,
ChevronDown,
Users,
} from "lucide-react";
import {
DropdownMenu,
@@ -63,6 +64,11 @@ export function Navigation({ userInfo }: NavigationProps = {}) {
label: "Évaluation",
icon: User,
},
{
href: "/team",
label: "Mon équipe",
icon: Users,
},
{
href: "/admin",
label: "Administration",

View File

@@ -0,0 +1,152 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TeamMemberProfile, SkillGap } from "@/lib/team-review-types";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { GraduationCap } from "lucide-react";
interface LearningSectionProps {
members: TeamMemberProfile[];
skillGaps: SkillGap[];
}
export function LearningSection({ members, skillGaps }: LearningSectionProps) {
// Trouver les apprenants
const learners = members.filter((member) => member.learningSkills > 0);
// Organiser les besoins d'apprentissage par apprenant
const learningNeeds = learners.map((learner) => {
const needs = learner.skills
.filter((skill) => skill.wantsToLearn)
.map((skill) => {
const gap = skillGaps.find((g) => g.skillId === skill.skillId);
if (!gap) return null;
const mentors = members.filter((member) =>
member.skills.some(
(s) =>
s.skillId === skill.skillId &&
s.canMentor &&
["autonomous", "expert"].includes(s.level)
)
);
return {
skill: skill.skillName,
category: skill.category,
currentLevel: skill.level,
mentors,
isHighRisk: gap.risk === "high",
};
})
.filter(Boolean);
return {
learner,
needs,
};
});
const getLevelColor = (level: string) => {
const colors = {
never: "bg-white/5 text-slate-300 border-white/20",
"not-autonomous": "bg-yellow-500/20 text-yellow-200 border-yellow-500/30",
autonomous: "bg-green-500/20 text-green-200 border-green-500/30",
expert: "bg-blue-500/20 text-blue-200 border-blue-500/30",
};
return colors[level as keyof typeof colors];
};
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">Besoins en formation</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px]">
<div className="space-y-6">
{learningNeeds.map(({ learner, needs }) => (
<div key={learner.member.uuid} className="space-y-4">
<div className="flex items-center gap-3">
<Avatar className="bg-white/10 border border-white/20">
<AvatarFallback className="text-slate-200 bg-transparent">
{learner.member.firstName[0]}
{learner.member.lastName[0]}
</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold text-slate-200">
{learner.member.firstName} {learner.member.lastName}
</h3>
<p className="text-sm text-slate-400">
{learner.learningSkills} compétences à développer
</p>
</div>
</div>
<div className="pl-12 space-y-3">
{needs.map(
(need, idx) =>
need && (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
<Badge
variant="outline"
className="bg-white/5 text-slate-300 border-white/20"
>
{need.category}
</Badge>
<span className="font-medium text-slate-300">
{need.skill}
</span>
{need.isHighRisk && (
<Badge
variant="outline"
className="bg-red-500/20 text-red-200 border-red-500/30"
>
Prioritaire
</Badge>
)}
</div>
<div className="flex items-center gap-2 text-sm">
<span className="text-slate-400">
Niveau actuel :
</span>
<Badge
variant="secondary"
className={getLevelColor(need.currentLevel)}
>
{need.currentLevel}
</Badge>
</div>
{need.mentors.length > 0 && (
<div className="pl-4">
<p className="text-sm text-slate-400 mb-2">
Mentors disponibles :
</p>
<div className="flex flex-wrap gap-2">
{need.mentors.map((mentor) => (
<Badge
key={mentor.member.uuid}
variant="outline"
className="bg-green-500/10 text-green-200 border-green-500/30"
>
{mentor.member.firstName}{" "}
{mentor.member.lastName}
</Badge>
))}
</div>
</div>
)}
</div>
)
)}
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,126 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TeamMemberProfile, SkillGap } from "@/lib/team-review-types";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { UserCheck } from "lucide-react";
interface MentorshipSectionProps {
members: TeamMemberProfile[];
skillGaps: SkillGap[];
}
export function MentorshipSection({
members,
skillGaps,
}: MentorshipSectionProps) {
// Trouver les mentors potentiels
const mentors = members.filter((member) => member.mentorSkills > 0);
// Organiser les opportunités de mentorat par mentor
const mentorshipOpportunities = mentors.map((mentor) => {
const opportunities = mentor.skills
.filter((skill) => skill.canMentor)
.map((skill) => {
const gap = skillGaps.find((g) => g.skillId === skill.skillId);
if (!gap) return null;
const learners = members.filter((member) =>
member.skills.some(
(s) =>
s.skillId === skill.skillId &&
s.wantsToLearn &&
["never", "not-autonomous"].includes(s.level)
)
);
return {
skill: skill.skillName,
category: skill.category,
learners,
};
})
.filter(Boolean);
return {
mentor,
opportunities,
};
});
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">
Opportunités de mentorat
</CardTitle>
</CardHeader>
<CardContent>
<ScrollArea className="h-[400px]">
<div className="space-y-6">
{mentorshipOpportunities.map(({ mentor, opportunities }) => (
<div key={mentor.member.uuid} className="space-y-4">
<div className="flex items-center gap-3">
<Avatar className="bg-white/10 border border-white/20">
<AvatarFallback className="text-slate-200 bg-transparent">
{mentor.member.firstName[0]}
{mentor.member.lastName[0]}
</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold text-slate-200">
{mentor.member.firstName} {mentor.member.lastName}
</h3>
<p className="text-sm text-slate-400">
{mentor.mentorSkills} compétences en mentorat
</p>
</div>
</div>
<div className="pl-12 space-y-3">
{opportunities.map(
(opp, idx) =>
opp && (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
<Badge
variant="outline"
className="bg-white/5 text-slate-300 border-white/20"
>
{opp.category}
</Badge>
<span className="font-medium text-slate-300">
{opp.skill}
</span>
</div>
{opp.learners.length > 0 && (
<div className="pl-4">
<p className="text-sm text-slate-400 mb-2">
Apprenants potentiels :
</p>
<div className="flex flex-wrap gap-2">
{opp.learners.map((learner) => (
<Badge
key={learner.member.uuid}
variant="outline"
className="bg-blue-500/10 text-blue-200 border-blue-500/30"
>
{learner.member.firstName}{" "}
{learner.member.lastName}
</Badge>
))}
</div>
</div>
)}
</div>
)
)}
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,199 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Badge } from "@/components/ui/badge";
import { TeamMemberProfile, SkillGap } from "@/lib/team-review-types";
import { UserCheck, GraduationCap } from "lucide-react";
import { TechIcon } from "@/components/icons/tech-icon";
interface SkillMatrixProps {
members: TeamMemberProfile[];
skillGaps: SkillGap[];
}
export function SkillMatrix({ members, skillGaps }: SkillMatrixProps) {
// Filtrer les skills valides et les organiser par catégorie
const validSkillGaps = skillGaps.filter(
(skill) => skill.skillId && skill.skillName && skill.category
);
const skillsByCategory = validSkillGaps.reduce((acc, skill) => {
if (!acc[skill.category]) {
acc[skill.category] = [];
}
acc[skill.category].push(skill);
return acc;
}, {} as Record<string, SkillGap[]>);
const getLevelBadge = (level: string | null) => {
const colors = {
never: "bg-white/5 text-slate-300",
"not-autonomous": "bg-yellow-500/20 text-yellow-200",
autonomous: "bg-green-500/20 text-green-200",
expert: "bg-blue-500/20 text-blue-200",
};
return level ? (
<Badge
variant="secondary"
className={colors[level as keyof typeof colors]}
>
{level}
</Badge>
) : (
<span className="text-slate-600">-</span>
);
};
// Vérifier si nous avons des données valides
if (validSkillGaps.length === 0 || members.length === 0) {
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">
Matrice des compétences
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-slate-400">
Aucune donnée disponible pour la matrice des compétences.
</p>
</CardContent>
</Card>
);
}
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">
Matrice des compétences
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-8">
{Object.entries(skillsByCategory).map(([category, skills]) => (
<div key={`category-${category}`}>
<h3 className="text-lg font-semibold text-slate-200 mb-4">
{category}
</h3>
<div className="rounded-md border border-white/10">
<Table>
<TableHeader>
<TableRow className="border-white/10">
<TableHead className="w-[200px] text-slate-300">
Compétence
</TableHead>
{members.map((member) => (
<TableHead
key={`header-${member.member.uuid}`}
className="text-slate-300"
>
{member.member.firstName} {member.member.lastName}
</TableHead>
))}
<TableHead className="w-[120px] text-slate-300">
Couverture
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{skills.map((skill) => (
<TableRow
key={`skill-row-${skill.skillId}-${skill.category}`}
className="border-white/10"
>
<TableCell className="font-medium">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-white/10 border border-white/20 flex items-center justify-center">
<TechIcon
iconName={skill.icon || ""}
className="w-4 h-4 text-blue-400"
fallbackText={skill.skillName}
/>
</div>
<div className="flex items-center gap-2">
<span className="text-slate-200">
{skill.skillName}
</span>
{skill.risk === "high" && (
<Badge
variant="destructive"
className="bg-red-500/20 text-red-200 border-red-500/30"
>
Risque
</Badge>
)}
</div>
</div>
</TableCell>
{members.map((member) => {
const memberSkill = member.skills.find(
(s) => s.skillId === skill.skillId
);
return (
<TableCell
key={`skill-${skill.skillId}-member-${member.member.uuid}`}
>
<div className="flex items-center gap-2 flex-wrap">
{getLevelBadge(memberSkill?.level || null)}
{memberSkill?.canMentor && (
<Badge
variant="outline"
className="flex items-center gap-1 bg-green-500/10 text-green-200 border-green-500/30"
>
<UserCheck className="h-3 w-3" />
<span className="text-xs">Mentor</span>
</Badge>
)}
{memberSkill?.wantsToLearn && (
<Badge
variant="outline"
className="flex items-center gap-1 bg-blue-500/10 text-blue-200 border-blue-500/30"
>
<GraduationCap className="h-3 w-3" />
<span className="text-xs">Apprenant</span>
</Badge>
)}
</div>
</TableCell>
);
})}
<TableCell>
<div className="flex items-center gap-2">
<div className="w-full bg-white/10 rounded-full h-2">
<div
className="bg-blue-500/50 h-2 rounded-full"
style={{
width: `${Math.max(
0,
Math.min(100, skill.coverage || 0)
)}%`,
}}
/>
</div>
<span className="text-sm text-slate-400 whitespace-nowrap">
{Math.round(skill.coverage || 0)}%
</span>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
))}
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,89 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { CategoryCoverage } from "@/lib/team-review-types";
import { Progress } from "@/components/ui/progress";
interface TeamInsightsProps {
recommendations: string[];
categoryCoverage: CategoryCoverage[];
}
export function TeamInsights({
recommendations,
categoryCoverage,
}: TeamInsightsProps) {
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">
Insights & Recommandations
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-4">
{recommendations.map((recommendation, index) => (
<Alert key={index} className="bg-white/5 border-white/10">
<AlertDescription className="text-slate-300">
{recommendation}
</AlertDescription>
</Alert>
))}
</div>
<div className="space-y-6">
<h3 className="font-semibold text-slate-200">
Couverture par catégorie
</h3>
<div className="grid grid-cols-2 gap-4">
{categoryCoverage.map((category) => (
<div key={category.category} className="space-y-2">
<div className="flex justify-between items-center">
<span
className="text-sm font-medium truncate text-slate-300"
title={category.category}
>
{category.category}
</span>
<span className="text-sm text-slate-400">
{Math.round(category.coverage)}%
</span>
</div>
<div className="h-2 w-full bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-blue-500/50 rounded-full transition-all"
style={{
width: `${Math.min(
100,
Math.max(0, category.coverage)
)}%`,
}}
/>
</div>
<div className="grid grid-cols-3 gap-2 text-xs text-slate-400">
<div>
<span className="font-medium text-slate-300">
{category.experts}
</span>{" "}
exp.
</div>
<div>
<span className="font-medium text-slate-300">
{category.mentors}
</span>{" "}
ment.
</div>
<div>
<span className="font-medium text-slate-300">
{category.learners}
</span>{" "}
app.
</div>
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,166 @@
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";
interface TeamOverviewProps {
team: TeamReviewData["team"];
stats: TeamReviewData["stats"];
members: TeamReviewData["members"];
categoryCoverage: TeamReviewData["categoryCoverage"];
}
export function TeamOverview({
team,
stats,
members,
categoryCoverage,
}: TeamOverviewProps) {
// Trouver les top contributeurs
const topContributors = [...members]
.sort((a, b) => b.expertSkills - a.expertSkills)
.slice(0, 3);
// Trouver les catégories les plus fortes
const strongestCategories = [...categoryCoverage]
.sort((a, b) => b.coverage - a.coverage)
.slice(0, 2);
// 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)
);
})
.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);
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">Vue d'ensemble</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{/* Points forts */}
<div>
<h3 className="text-sm font-medium text-slate-200 mb-4">
Points forts
</h3>
<div className="space-y-4">
{strongestCategories.map((cat) => (
<div key={cat.category} className="space-y-2">
<div className="flex justify-between items-center">
<p className="text-sm font-medium text-slate-300">
{cat.category}
</p>
<span className="text-sm text-slate-400">
{cat.coverage.toFixed(0)}%
</span>
</div>
<Progress value={cat.coverage} className="bg-white/10" />
<p className="text-xs text-slate-400">
{cat.experts} experts • {cat.mentors} mentors
</p>
</div>
))}
</div>
</div>
{/* Top contributeurs */}
<div>
<h3 className="text-sm font-medium text-slate-200 mb-4">
Top contributeurs
</h3>
<div className="space-y-4">
{topContributors.map((member) => (
<div key={member.member.uuid} className="space-y-1">
<p className="text-sm font-medium text-slate-300">
{member.member.firstName} {member.member.lastName}
</p>
<div className="flex gap-2 text-xs">
<Badge
variant="secondary"
className="bg-white/10 text-slate-300 border-white/20"
>
{member.expertSkills} expertises
</Badge>
{member.mentorSkills > 0 && (
<Badge
variant="outline"
className="text-slate-300 border-white/20"
>
{member.mentorSkills} mentorats
</Badge>
)}
</div>
</div>
))}
</div>
</div>
{/* Points d'attention */}
<div>
<h3 className="text-sm font-medium flex items-center gap-2 text-amber-400 mb-4">
<AlertTriangle size={16} />
Besoins prioritaires
</h3>
<div className="space-y-4">
{categoriesNeedingAttention.map((cat) => (
<div key={cat.category} className="space-y-2">
<div className="flex justify-between items-center">
<p className="text-sm font-medium text-slate-300">
{cat.category}
</p>
<div className="flex items-center gap-2">
<Badge
variant="outline"
className="text-amber-400 border-amber-400/30"
>
{cat.learners} apprenants
</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>
</div>
))}
{stats.learningNeeds > 0 && (
<div className="pt-2 border-t border-white/10">
<p className="text-sm text-amber-400 flex items-center gap-2">
<AlertTriangle size={16} />
{stats.learningNeeds} compétences sans expert ni mentor
</p>
</div>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,285 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TeamReviewData } from "@/lib/team-review-types";
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
} from "recharts";
interface TeamStatsProps {
stats: TeamReviewData["stats"];
members: TeamReviewData["members"];
skillGaps: TeamReviewData["skillGaps"];
categoryCoverage: TeamReviewData["categoryCoverage"];
}
export function TeamStats({
stats,
members,
skillGaps,
categoryCoverage,
}: TeamStatsProps) {
// Calcul des statistiques avancées
const totalEvaluations = members.reduce(
(acc, member) => acc + member.skills.length,
0
);
const avgSkillsPerMember = totalEvaluations / (stats.totalMembers || 1);
// Distribution des niveaux
const levelDistribution = members.reduce((acc, member) => {
member.skills.forEach((skill) => {
acc[skill.level] = (acc[skill.level] || 0) + 1;
});
return acc;
}, {} as Record<string, number>);
const levelData = [
{ name: "Expert", value: levelDistribution.expert || 0, color: "#4f46e5" },
{
name: "Autonome",
value: levelDistribution.autonomous || 0,
color: "#10b981",
},
{
name: "En apprentissage",
value: levelDistribution["not-autonomous"] || 0,
color: "#f59e0b",
},
{
name: "Jamais pratiqué",
value: levelDistribution.never || 0,
color: "#6b7280",
},
];
// Top catégories par expertise
const sortedCategories = [...categoryCoverage].sort(
(a, b) => b.experts - a.experts
);
const topCategories = sortedCategories.slice(0, 3);
// Statistiques de mentorat
const mentorCount = members.filter((m) => m.mentorSkills > 0).length;
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>);
const gapData = Object.entries(criticalGapsByCategory).map(
([category, count]) => ({
name: category,
value: count,
fill: "#ef4444",
})
);
return (
<Card className="bg-white/5 border-white/10 backdrop-blur">
<CardHeader>
<CardTitle className="text-slate-200">
Statistiques de l'équipe
</CardTitle>
</CardHeader>
<CardContent className="space-y-8">
{/* Vue d'ensemble */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1">
<p className="text-sm text-slate-400">Membres</p>
<p className="text-2xl font-bold text-slate-200">
{stats.totalMembers}
</p>
</div>
<div className="space-y-1">
<p className="text-sm text-slate-400">Compétences/membre</p>
<p className="text-2xl font-bold text-slate-200">
{avgSkillsPerMember.toFixed(1)}
</p>
</div>
<div className="space-y-1">
<p className="text-sm text-slate-400">Ratio mentors</p>
<p className="text-2xl font-bold text-slate-200">
{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>
</div>
</div>
{/* Distribution des niveaux */}
<div>
<h3 className="text-sm font-medium text-slate-200 mb-4">
Distribution des niveaux
</h3>
<div className="h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={levelData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
outerRadius={80}
label={(entry) => `${entry.name} (${entry.value})`}
labelLine={{ stroke: "#64748b" }}
>
{levelData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.color}
className="opacity-80"
/>
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: "rgba(30, 41, 59, 0.9)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "8px",
}}
itemStyle={{ color: "#e2e8f0" }}
labelStyle={{ color: "#e2e8f0" }}
/>
</PieChart>
</ResponsiveContainer>
</div>
</div>
{/* Top catégories */}
<div>
<h3 className="text-sm font-medium text-slate-200 mb-4">
Top catégories par expertise
</h3>
<div className="space-y-3">
{topCategories.map((cat) => (
<div key={cat.category} className="flex items-center gap-4">
<div className="flex-1">
<p className="text-sm font-medium text-slate-300">
{cat.category}
</p>
<div className="flex gap-2 text-sm text-slate-400">
<span>{cat.experts} experts</span>
<span></span>
<span>{cat.mentors} mentors</span>
</div>
</div>
<div className="w-24 text-right">
<p className="text-sm font-medium text-slate-300">
{cat.coverage.toFixed(0)}%
</p>
<p className="text-sm text-slate-400">couverture</p>
</div>
</div>
))}
</div>
</div>
{/* Gaps critiques par catégorie */}
{gapData.length > 0 && (
<div>
<h3 className="text-sm font-medium text-slate-200 mb-4">
Gaps critiques par catégorie
</h3>
<div className="h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={gapData}>
<CartesianGrid
strokeDasharray="3 3"
stroke="rgba(255, 255, 255, 0.1)"
/>
<XAxis
dataKey="name"
tick={{ fill: "#94a3b8" }}
axisLine={{ stroke: "#334155" }}
/>
<YAxis
tick={{ fill: "#94a3b8" }}
axisLine={{ stroke: "#334155" }}
/>
<Tooltip
contentStyle={{
backgroundColor: "rgba(30, 41, 59, 0.9)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "8px",
}}
itemStyle={{ color: "#e2e8f0" }}
labelStyle={{ color: "#e2e8f0" }}
/>
<Bar dataKey="value" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
)}
{/* Statistiques de mentorat */}
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-white/10">
<div>
<h3 className="text-sm font-medium text-slate-200 mb-2">
Mentorat
</h3>
<div className="space-y-1">
<div className="flex justify-between">
<p className="text-sm text-slate-400">Mentors</p>
<p className="text-sm font-medium text-slate-300">
{mentorCount}
</p>
</div>
<div className="flex justify-between">
<p className="text-sm text-slate-400">Apprenants</p>
<p className="text-sm font-medium text-slate-300">
{learnerCount}
</p>
</div>
<div className="flex justify-between">
<p className="text-sm text-slate-400">Opportunités</p>
<p className="text-sm font-medium text-slate-300">
{stats.mentorshipOpportunities}
</p>
</div>
</div>
</div>
<div>
<h3 className="text-sm font-medium text-slate-200 mb-2">
Couverture globale
</h3>
<div className="space-y-1">
<div className="flex justify-between">
<p className="text-sm text-slate-400">Compétences</p>
<p className="text-sm font-medium text-slate-300">
{stats.totalSkills}
</p>
</div>
<div className="flex justify-between">
<p className="text-sm text-slate-400">Niveau moyen</p>
<p className="text-sm font-medium text-slate-300">
{((stats.averageTeamLevel * 100) / 3).toFixed(0)}%
</p>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
}