316 lines
12 KiB
TypeScript
316 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import { Building2, Users, TrendingUp, BarChart3, Target } from "lucide-react";
|
|
import {
|
|
TeamStatsRow,
|
|
getSkillLevelLabel,
|
|
getSkillLevelColor,
|
|
} from "../team-detail/team-stats-row";
|
|
|
|
interface DirectionOverviewProps {
|
|
direction: string;
|
|
teams: Array<{
|
|
teamId: string;
|
|
teamName: string;
|
|
direction: string;
|
|
totalMembers: number;
|
|
averageSkillLevel: number;
|
|
topSkills: Array<{
|
|
skillName: string;
|
|
averageLevel: number;
|
|
importance: "incontournable" | "majeure" | "standard";
|
|
coverage: number;
|
|
}>;
|
|
skillCoverage: number;
|
|
criticalSkillsCoverage: {
|
|
incontournable: number;
|
|
majeure: number;
|
|
};
|
|
}>;
|
|
totalMembers: number;
|
|
averageSkillLevel: number;
|
|
topCategories: Array<{ category: string; averageLevel: number }>;
|
|
onViewTeamDetails?: (team: any) => void;
|
|
onExportTeamReport?: (team: any) => void;
|
|
}
|
|
|
|
// Fonction pour obtenir les couleurs de direction
|
|
function getDirectionColors(direction: string) {
|
|
const directionLower = direction.toLowerCase();
|
|
if (directionLower.includes("tech") || directionLower.includes("dev")) {
|
|
return {
|
|
header: "from-blue-600/30 to-cyan-600/30",
|
|
border: "border-blue-500/40",
|
|
icon: "bg-blue-500/20 border-blue-500/30",
|
|
};
|
|
} else if (
|
|
directionLower.includes("design") ||
|
|
directionLower.includes("ux")
|
|
) {
|
|
return {
|
|
header: "from-purple-600/30 to-pink-600/30",
|
|
border: "border-purple-500/40",
|
|
icon: "bg-purple-500/20 border-purple-500/30",
|
|
};
|
|
} else if (
|
|
directionLower.includes("data") ||
|
|
directionLower.includes("analytics")
|
|
) {
|
|
return {
|
|
header: "from-emerald-600/30 to-teal-600/30",
|
|
border: "border-emerald-500/40",
|
|
icon: "bg-emerald-500/20 border-emerald-500/30",
|
|
};
|
|
} else if (
|
|
directionLower.includes("security") ||
|
|
directionLower.includes("sec")
|
|
) {
|
|
return {
|
|
header: "from-red-600/30 to-orange-600/30",
|
|
border: "border-red-500/40",
|
|
icon: "bg-red-500/20 border-red-500/30",
|
|
};
|
|
} else {
|
|
return {
|
|
header: "from-purple-600/30 to-blue-600/30",
|
|
border: "border-purple-500/40",
|
|
icon: "bg-purple-500/20 border-purple-500/30",
|
|
};
|
|
}
|
|
}
|
|
|
|
export function DirectionOverview({
|
|
direction,
|
|
teams,
|
|
totalMembers,
|
|
averageSkillLevel,
|
|
topCategories,
|
|
onViewTeamDetails = () => {},
|
|
onExportTeamReport = () => {},
|
|
}: DirectionOverviewProps) {
|
|
const colors = getDirectionColors(direction);
|
|
|
|
// Calculer la moyenne des couvertures des compétences critiques
|
|
const averageCriticalCoverage = teams.reduce(
|
|
(acc, team) => {
|
|
acc.incontournable += team.criticalSkillsCoverage.incontournable;
|
|
acc.majeure += team.criticalSkillsCoverage.majeure;
|
|
return acc;
|
|
},
|
|
{ incontournable: 0, majeure: 0 }
|
|
);
|
|
averageCriticalCoverage.incontournable /= teams.length || 1;
|
|
averageCriticalCoverage.majeure /= teams.length || 1;
|
|
|
|
return (
|
|
<div className="bg-gradient-to-br from-slate-800/40 to-slate-700/30 backdrop-blur-sm border border-slate-600/30 rounded-2xl overflow-hidden shadow-xl">
|
|
<div
|
|
className={`bg-gradient-to-r ${colors.header} border-b ${colors.border} p-6`}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-2 ${colors.icon} rounded-xl`}>
|
|
<Building2 className="h-5 w-5 text-white" />
|
|
</div>
|
|
<h2 className="text-xl font-bold text-white">
|
|
Direction {direction}
|
|
</h2>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2 px-3 py-2 bg-green-500/30 border border-green-500/40 rounded-xl">
|
|
<Users className="h-3 w-3 text-green-300" />
|
|
<span className="text-sm text-green-200 font-medium">
|
|
{totalMembers}
|
|
</span>
|
|
</div>
|
|
<div className="px-3 py-2 bg-blue-500/30 border border-blue-500/40 rounded-xl">
|
|
<span className="text-sm text-blue-200 font-medium">
|
|
Niveau: {getSkillLevelLabel(averageSkillLevel)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-6">
|
|
{/* Direction Metrics */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
{/* Top Categories */}
|
|
<div className="bg-gradient-to-br from-slate-800/50 to-slate-700/40 border border-slate-600/40 rounded-xl p-4 shadow-lg">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<BarChart3 className="h-4 w-4 text-slate-300" />
|
|
<h4 className="text-sm font-medium text-slate-200">
|
|
Top Catégories
|
|
</h4>
|
|
</div>
|
|
<div className="space-y-3">
|
|
{topCategories.slice(0, 4).map((cat, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="flex items-center justify-between p-2 bg-slate-700/50 rounded-lg border border-slate-600/30"
|
|
>
|
|
<span className="text-sm text-white">{cat.category}</span>
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={`w-2 h-2 rounded-full ${getSkillLevelColor(
|
|
cat.averageLevel
|
|
)}`}
|
|
/>
|
|
<span className="text-xs font-medium text-slate-200 min-w-8 text-right">
|
|
{cat.averageLevel.toFixed(1)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Overall Progress */}
|
|
<div className="bg-gradient-to-br from-slate-800/50 to-slate-700/40 border border-slate-600/40 rounded-xl p-4 shadow-lg">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<TrendingUp className="h-4 w-4 text-slate-300" />
|
|
<h4 className="text-sm font-medium text-slate-200">
|
|
Progression Globale
|
|
</h4>
|
|
</div>
|
|
<div className="space-y-3">
|
|
{/* Compétences incontournables */}
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm text-slate-300">
|
|
Incontournables:
|
|
</span>
|
|
<span
|
|
className={`text-sm font-bold ${
|
|
averageCriticalCoverage.incontournable < 75
|
|
? "text-red-400"
|
|
: "text-green-400"
|
|
}`}
|
|
>
|
|
{averageCriticalCoverage.incontournable.toFixed(0)}%
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-slate-700/50 rounded-full h-1.5">
|
|
<div
|
|
className={`h-1.5 rounded-full transition-all shadow-sm ${
|
|
averageCriticalCoverage.incontournable < 75
|
|
? "bg-gradient-to-r from-red-500 to-red-400"
|
|
: "bg-gradient-to-r from-green-500 to-green-400"
|
|
}`}
|
|
style={{
|
|
width: `${averageCriticalCoverage.incontournable}%`,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Compétences majeures */}
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm text-slate-300">Majeures:</span>
|
|
<span
|
|
className={`text-sm font-bold ${
|
|
averageCriticalCoverage.majeure < 60
|
|
? "text-orange-400"
|
|
: "text-green-400"
|
|
}`}
|
|
>
|
|
{averageCriticalCoverage.majeure.toFixed(0)}%
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-slate-700/50 rounded-full h-1.5">
|
|
<div
|
|
className={`h-1.5 rounded-full transition-all shadow-sm ${
|
|
averageCriticalCoverage.majeure < 60
|
|
? "bg-gradient-to-r from-orange-500 to-orange-400"
|
|
: "bg-gradient-to-r from-green-500 to-green-400"
|
|
}`}
|
|
style={{ width: `${averageCriticalCoverage.majeure}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Niveau global */}
|
|
<div className="space-y-2 pt-2 border-t border-slate-600/30">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm text-slate-300">Niveau global:</span>
|
|
<span className="text-sm font-bold text-white">
|
|
{((averageSkillLevel / 3) * 100).toFixed(0)}%
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-slate-700/50 rounded-full h-1.5">
|
|
<div
|
|
className="bg-gradient-to-r from-blue-500 to-blue-400 h-1.5 rounded-full transition-all shadow-sm"
|
|
style={{ width: `${(averageSkillLevel / 3) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Team Distribution */}
|
|
<div className="bg-gradient-to-br from-slate-800/50 to-slate-700/40 border border-slate-600/40 rounded-xl p-4 shadow-lg">
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Target className="h-4 w-4 text-slate-300" />
|
|
<h4 className="text-sm font-medium text-slate-200">
|
|
Répartition
|
|
</h4>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-white">
|
|
{teams.length}
|
|
</div>
|
|
<div className="text-xs text-slate-300">équipes actives</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between text-xs">
|
|
<span className="text-slate-300">Membres par équipe:</span>
|
|
<span className="text-white font-medium">
|
|
{(totalMembers / teams.length).toFixed(1)}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between text-xs">
|
|
<span className="text-slate-300">Équipes performantes:</span>
|
|
<span className="text-white font-medium">
|
|
{teams.filter((t) => t.averageSkillLevel > 2).length}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Teams List */}
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-500/30 border border-blue-500/40 rounded-xl">
|
|
<Users className="h-4 w-4 text-blue-300" />
|
|
</div>
|
|
<h4 className="text-lg font-semibold text-white">
|
|
Équipes de la direction
|
|
</h4>
|
|
</div>
|
|
<div className="space-y-4">
|
|
{teams.map((team) => (
|
|
<TeamStatsRow
|
|
key={team.teamId}
|
|
teamId={team.teamId}
|
|
teamName={team.teamName}
|
|
direction={team.direction}
|
|
totalMembers={team.totalMembers}
|
|
averageSkillLevel={team.averageSkillLevel}
|
|
topSkills={team.topSkills}
|
|
skillCoverage={team.skillCoverage}
|
|
criticalSkillsCoverage={team.criticalSkillsCoverage}
|
|
onViewDetails={() => onViewTeamDetails(team)}
|
|
onViewReport={() => onExportTeamReport(team)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|