feat: review admin overview and popup details fwith importance
This commit is contained in:
@@ -28,8 +28,14 @@ interface TeamDetailModalProps {
|
||||
skillName: string;
|
||||
averageLevel: number;
|
||||
icon?: string;
|
||||
importance: "incontournable" | "majeure" | "standard";
|
||||
coverage: number;
|
||||
}>;
|
||||
skillCoverage: number;
|
||||
criticalSkillsCoverage: {
|
||||
incontournable: number;
|
||||
majeure: number;
|
||||
};
|
||||
members: TeamMember[];
|
||||
} | null;
|
||||
}
|
||||
@@ -113,27 +119,88 @@ export function TeamDetailModal({
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Stats générales */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4 text-center">
|
||||
<Users className="h-6 w-6 text-green-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{team.totalMembers}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4">
|
||||
<h3 className="text-sm font-medium text-slate-300 mb-3">
|
||||
Équipe
|
||||
</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 flex items-center gap-3">
|
||||
<Users className="h-5 w-5 text-green-400" />
|
||||
<div>
|
||||
<div className="text-lg font-bold text-white">
|
||||
{team.totalMembers}
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">Membres</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center gap-3">
|
||||
<Eye className="h-5 w-5 text-blue-400" />
|
||||
<div>
|
||||
<div className="text-lg font-bold text-white">
|
||||
{((team.averageSkillLevel / 3) * 100).toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">Niveau moyen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">Membres</div>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4 text-center">
|
||||
<Eye className="h-6 w-6 text-blue-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{((team.averageSkillLevel / 3) * 100).toFixed(0)}%
|
||||
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4">
|
||||
<h3 className="text-sm font-medium text-slate-300 mb-3">
|
||||
Couverture
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-slate-300">
|
||||
Incontournables
|
||||
</span>
|
||||
<span
|
||||
className={`text-sm font-bold ${
|
||||
team.criticalSkillsCoverage.incontournable < 75
|
||||
? "text-red-400"
|
||||
: "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{team.criticalSkillsCoverage.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 ${
|
||||
team.criticalSkillsCoverage.incontournable < 75
|
||||
? "bg-red-500"
|
||||
: "bg-green-500"
|
||||
}`}
|
||||
style={{
|
||||
width: `${team.criticalSkillsCoverage.incontournable}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-slate-300">Majeures</span>
|
||||
<span
|
||||
className={`text-sm font-bold ${
|
||||
team.criticalSkillsCoverage.majeure < 60
|
||||
? "text-red-400"
|
||||
: "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{team.criticalSkillsCoverage.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 ${
|
||||
team.criticalSkillsCoverage.majeure < 60
|
||||
? "bg-red-500"
|
||||
: "bg-green-500"
|
||||
}`}
|
||||
style={{ width: `${team.criticalSkillsCoverage.majeure}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">Niveau moyen</div>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4 text-center">
|
||||
<ExternalLink className="h-6 w-6 text-orange-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{team.skillCoverage.toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">Couverture</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -141,24 +208,65 @@ export function TeamDetailModal({
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4">
|
||||
<h3 className="font-medium text-white mb-3">Top 3 Compétences</h3>
|
||||
<div className="space-y-2">
|
||||
{team.topSkills.slice(0, 3).map((skill, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between p-2 bg-white/5 rounded-lg"
|
||||
>
|
||||
<span className="text-white text-sm">{skill.skillName}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-slate-400">
|
||||
{((skill.averageLevel / 3) * 100).toFixed(0)}%
|
||||
</span>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${getSkillLevelColor(
|
||||
skill.averageLevel
|
||||
)}`}
|
||||
/>
|
||||
{team.topSkills.slice(0, 3).map((skill, idx) => {
|
||||
const target =
|
||||
skill.importance === "incontournable"
|
||||
? 75
|
||||
: skill.importance === "majeure"
|
||||
? 60
|
||||
: 0;
|
||||
const isUnderTarget = target > 0 && skill.coverage < target;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between p-2 bg-white/5 rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white text-sm">
|
||||
{skill.skillName}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={
|
||||
skill.importance === "incontournable"
|
||||
? "border-red-500/30 text-red-400 text-[10px]"
|
||||
: skill.importance === "majeure"
|
||||
? "border-blue-500/30 text-blue-400 text-[10px]"
|
||||
: "border-slate-500/30 text-slate-400 text-[10px]"
|
||||
}
|
||||
>
|
||||
{skill.importance === "incontournable"
|
||||
? "Incontournable"
|
||||
: skill.importance === "majeure"
|
||||
? "Majeure"
|
||||
: "Standard"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`text-xs ${
|
||||
isUnderTarget ? "text-red-400" : "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{skill.coverage.toFixed(0)}%
|
||||
</span>
|
||||
{target > 0 && (
|
||||
<span className="text-[10px] text-slate-500">
|
||||
(obj. {target}%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${getSkillLevelColor(
|
||||
skill.averageLevel
|
||||
)}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ import {
|
||||
User,
|
||||
Zap,
|
||||
Crown,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { TechIcon } from "@/components/icons/tech-icon";
|
||||
import { getImportanceColors } from "@/lib/tech-colors";
|
||||
|
||||
interface TeamStatsRowProps {
|
||||
teamId: string;
|
||||
@@ -34,8 +36,14 @@ interface TeamStatsRowProps {
|
||||
averageLevel: number;
|
||||
color?: string;
|
||||
icon?: string;
|
||||
importance: "incontournable" | "majeure" | "standard";
|
||||
coverage: number;
|
||||
}>;
|
||||
skillCoverage: number;
|
||||
criticalSkillsCoverage: {
|
||||
incontournable: number;
|
||||
majeure: number;
|
||||
};
|
||||
onViewDetails?: () => void;
|
||||
onViewReport?: () => void;
|
||||
}
|
||||
@@ -77,9 +85,14 @@ export function TeamStatsRow({
|
||||
averageSkillLevel,
|
||||
topSkills,
|
||||
skillCoverage,
|
||||
criticalSkillsCoverage,
|
||||
onViewDetails,
|
||||
onViewReport,
|
||||
}: TeamStatsRowProps) {
|
||||
// Calculer les alertes sur les compétences critiques
|
||||
const hasIncontournableAlert = criticalSkillsCoverage.incontournable < 75;
|
||||
const hasMajeureAlert = criticalSkillsCoverage.majeure < 60;
|
||||
|
||||
return (
|
||||
<div className="group bg-gradient-to-r from-slate-800/50 to-slate-700/40 backdrop-blur-sm border border-slate-600/40 rounded-xl p-4 hover:from-slate-700/60 hover:to-slate-600/50 hover:border-slate-500/50 transition-all duration-300 shadow-lg hover:shadow-xl">
|
||||
{/* Layout horizontal compact */}
|
||||
@@ -125,19 +138,57 @@ export function TeamStatsRow({
|
||||
|
||||
{/* Indicateurs clés compacts */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-bold text-white">
|
||||
{averageSkillLevel.toFixed(1)}
|
||||
</div>
|
||||
<div className="text-xs text-slate-300">/ 3.0</div>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="text-center">
|
||||
<div
|
||||
className={`text-sm font-bold ${
|
||||
criticalSkillsCoverage.incontournable < 75
|
||||
? "text-red-400"
|
||||
: "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{criticalSkillsCoverage.incontournable.toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-slate-300">Incont.</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
Couverture des compétences incontournables
|
||||
<br />
|
||||
Objectif : 75%
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-bold text-white">
|
||||
{skillCoverage.toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-slate-300">Couv.</div>
|
||||
</div>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="text-center">
|
||||
<div
|
||||
className={`text-sm font-bold ${
|
||||
criticalSkillsCoverage.majeure < 60
|
||||
? "text-red-400"
|
||||
: "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{criticalSkillsCoverage.majeure.toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-xs text-slate-300">Maj.</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">
|
||||
Couverture des compétences majeures
|
||||
<br />
|
||||
Objectif : 60%
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -169,21 +220,68 @@ export function TeamStatsRow({
|
||||
|
||||
{/* Top skills mini */}
|
||||
<div className="flex items-center gap-2">
|
||||
{topSkills.slice(0, 3).map((skill, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`px-2 py-1 border rounded-md text-center min-w-[60px] shadow-sm ${getSkillLevelBadgeClasses(
|
||||
skill.averageLevel
|
||||
)}`}
|
||||
>
|
||||
<div className="text-xs font-medium text-white truncate mb-1">
|
||||
{skill.skillName}
|
||||
</div>
|
||||
<div className="text-xs font-bold">
|
||||
{((skill.averageLevel / 3) * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{topSkills.slice(0, 3).map((skill, idx) => {
|
||||
const colors = getImportanceColors(skill.importance);
|
||||
const target =
|
||||
skill.importance === "incontournable"
|
||||
? 75
|
||||
: skill.importance === "majeure"
|
||||
? 60
|
||||
: 0;
|
||||
const isUnderTarget = target > 0 && skill.coverage < target;
|
||||
|
||||
return (
|
||||
<TooltipProvider key={idx}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`relative px-2 py-1 border rounded-md text-center min-w-[60px] shadow-sm ${
|
||||
isUnderTarget
|
||||
? "bg-red-500/20 border-red-500/30"
|
||||
: "bg-green-500/20 border-green-500/30"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`text-xs font-medium truncate mb-1 ${
|
||||
isUnderTarget ? "text-red-400" : "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{skill.skillName}
|
||||
</div>
|
||||
<div
|
||||
className={`text-xs font-bold ${
|
||||
isUnderTarget ? "text-red-400" : "text-green-400"
|
||||
}`}
|
||||
>
|
||||
{skill.coverage.toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="text-xs">
|
||||
<p className="font-medium">{skill.skillName}</p>
|
||||
<p className="text-slate-400">
|
||||
{skill.importance === "incontournable"
|
||||
? "Compétence incontournable"
|
||||
: skill.importance === "majeure"
|
||||
? "Compétence majeure"
|
||||
: "Compétence standard"}
|
||||
</p>
|
||||
{target > 0 && (
|
||||
<p
|
||||
className={
|
||||
isUnderTarget ? "text-red-400" : "text-green-400"
|
||||
}
|
||||
>
|
||||
Objectif : {target}%
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Actions compactes */}
|
||||
|
||||
Reference in New Issue
Block a user