feat: update TeamDetailPage and modularize components
- Changed params in TeamDetailPage to be a Promise, ensuring proper async handling. - Updated data fetching logic to use awaited teamId for better clarity. - Modularized TeamDetailClientWrapper by extracting TeamDetailHeader, TeamMetricsCards, TeamDetailTabs, and TeamMemberModal for improved organization and readability. - Removed unused imports and streamlined the component structure, enhancing maintainability.
This commit is contained in:
196
components/admin/team-skills-tab.tsx
Normal file
196
components/admin/team-skills-tab.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface SkillAnalysis {
|
||||
skillName: string;
|
||||
category: string;
|
||||
experts: Array<{
|
||||
name: string;
|
||||
level: number;
|
||||
canMentor: boolean;
|
||||
}>;
|
||||
learners: Array<{
|
||||
name: string;
|
||||
currentLevel: number;
|
||||
}>;
|
||||
averageLevel: number;
|
||||
totalEvaluations: number;
|
||||
expertCount: number;
|
||||
learnerCount: number;
|
||||
proficiencyRate: number;
|
||||
}
|
||||
|
||||
interface TeamSkillsTabProps {
|
||||
skillAnalysis: SkillAnalysis[];
|
||||
}
|
||||
|
||||
function getSkillLevelLabel(level: number): string {
|
||||
if (level < 0.5) return "Débutant";
|
||||
if (level < 1.5) return "Intermé.";
|
||||
if (level < 2.5) return "Avancé";
|
||||
return "Expert";
|
||||
}
|
||||
|
||||
function getSkillLevelColor(level: number): string {
|
||||
if (level < 0.5) return "bg-red-500";
|
||||
if (level < 1.5) return "bg-orange-500";
|
||||
if (level < 2.5) return "bg-blue-500";
|
||||
return "bg-green-500";
|
||||
}
|
||||
|
||||
function getSkillLevelBadgeClasses(level: number): string {
|
||||
if (level < 0.5) return "bg-red-500/20 border-red-500/30 text-red-300";
|
||||
if (level < 1.5)
|
||||
return "bg-orange-500/20 border-orange-500/30 text-orange-300";
|
||||
if (level < 2.5) return "bg-blue-500/20 border-blue-500/30 text-blue-300";
|
||||
return "bg-green-500/20 border-green-500/30 text-green-300";
|
||||
}
|
||||
|
||||
export function TeamSkillsTab({ skillAnalysis }: TeamSkillsTabProps) {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("all");
|
||||
|
||||
const categories = Array.from(new Set(skillAnalysis.map((s) => s.category)));
|
||||
const filteredSkills =
|
||||
selectedCategory === "all"
|
||||
? skillAnalysis
|
||||
: skillAnalysis.filter((s) => s.category === selectedCategory);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Filtres par catégorie */}
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant={selectedCategory === "all" ? "default" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setSelectedCategory("all")}
|
||||
className={
|
||||
selectedCategory === "all"
|
||||
? "bg-blue-500 text-white"
|
||||
: "text-slate-400 hover:text-white hover:bg-white/10"
|
||||
}
|
||||
>
|
||||
Toutes ({skillAnalysis.length})
|
||||
</Button>
|
||||
{categories.map((category) => (
|
||||
<Button
|
||||
key={category}
|
||||
variant={selectedCategory === category ? "default" : "ghost"}
|
||||
size="sm"
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={
|
||||
selectedCategory === category
|
||||
? "bg-blue-500 text-white"
|
||||
: "text-slate-400 hover:text-white hover:bg-white/10"
|
||||
}
|
||||
>
|
||||
{category} (
|
||||
{skillAnalysis.filter((s) => s.category === category).length})
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Liste des compétences détaillée */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{filteredSkills.map((skill, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="bg-white/5 border border-white/10 rounded-xl p-4 hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h4 className="font-medium text-white">{skill.skillName}</h4>
|
||||
<p className="text-xs text-slate-400">{skill.category}</p>
|
||||
</div>
|
||||
<div
|
||||
className={`px-2 py-1 border rounded-lg text-center ${getSkillLevelBadgeClasses(
|
||||
skill.averageLevel
|
||||
)}`}
|
||||
>
|
||||
<span className="text-xs font-medium">
|
||||
{getSkillLevelLabel(skill.averageLevel)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-slate-300">Niveau moyen:</span>
|
||||
<span className="text-white font-semibold">
|
||||
{skill.averageLevel.toFixed(1)}/3
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-white/10 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all ${getSkillLevelColor(
|
||||
skill.averageLevel
|
||||
)
|
||||
.replace("bg-", "bg-gradient-to-r from-")
|
||||
.replace("-500", "-500 to-")
|
||||
.replace("500 to-", "400")}`}
|
||||
style={{ width: `${(skill.averageLevel / 3) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2 text-xs">
|
||||
<div className="text-center">
|
||||
<div className="text-white font-semibold">
|
||||
{skill.totalEvaluations}
|
||||
</div>
|
||||
<div className="text-slate-400">Évaluations</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-green-300 font-semibold">
|
||||
{skill.expertCount}
|
||||
</div>
|
||||
<div className="text-slate-400">Experts</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-blue-300 font-semibold">
|
||||
{skill.learnerCount}
|
||||
</div>
|
||||
<div className="text-slate-400">Apprenants</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{skill.experts.filter((e) => e.canMentor).length > 0 && (
|
||||
<div className="pt-2 border-t border-white/10">
|
||||
<div className="text-xs text-slate-400 mb-1">
|
||||
Mentors disponibles:
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{skill.experts
|
||||
.filter((e) => e.canMentor)
|
||||
.slice(0, 2)
|
||||
.map((expert, i) => (
|
||||
<Badge
|
||||
key={i}
|
||||
variant="outline"
|
||||
className="text-xs text-green-300 border-green-500/30"
|
||||
>
|
||||
{expert.name}
|
||||
</Badge>
|
||||
))}
|
||||
{skill.experts.filter((e) => e.canMentor).length > 2 && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs text-slate-400 border-slate-500/30"
|
||||
>
|
||||
+{skill.experts.filter((e) => e.canMentor).length - 2}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user