diff --git a/app/admin/team/[teamId]/page.tsx b/app/admin/team/[teamId]/page.tsx index e48ab04..96b4aff 100644 --- a/app/admin/team/[teamId]/page.tsx +++ b/app/admin/team/[teamId]/page.tsx @@ -4,12 +4,15 @@ import { AdminService, TeamStats } from "@/services/admin-service"; import { TeamDetailClientWrapper } from "@/components/admin"; interface TeamDetailPageProps { - params: { + params: Promise<{ teamId: string; - }; + }>; } export default async function TeamDetailPage({ params }: TeamDetailPageProps) { + // Await params before using + const { teamId } = await params; + // Vérifier l'authentification const isAuthenticated = await isUserAuthenticated(); @@ -21,17 +24,13 @@ export default async function TeamDetailPage({ params }: TeamDetailPageProps) { try { // Charger les données côté serveur const allTeamsStats = await AdminService.getTeamsStats(); - const foundTeamStats = allTeamsStats.find( - (t) => t.teamId === params.teamId - ); + const foundTeamStats = allTeamsStats.find((t) => t.teamId === teamId); if (!foundTeamStats) { redirect("/admin"); } - return ( - - ); + return ; } catch (error) { console.error("Failed to load team data:", error); return ( diff --git a/components/admin/index.ts b/components/admin/index.ts index 562ca00..6aede39 100644 --- a/components/admin/index.ts +++ b/components/admin/index.ts @@ -7,3 +7,11 @@ export { AdminHeader } from "./admin-header"; export { AdminOverviewCards } from "./admin-overview-cards"; export { AdminFilters } from "./admin-filters"; export { AdminContentTabs } from "./admin-content-tabs"; +export { TeamDetailHeader } from "./team-detail-header"; +export { TeamMetricsCards } from "./team-metrics-cards"; +export { TeamDetailTabs } from "./team-detail-tabs"; +export { TeamOverviewTab } from "./team-overview-tab"; +export { TeamSkillsTab } from "./team-skills-tab"; +export { TeamMembersTab } from "./team-members-tab"; +export { TeamInsightsTab } from "./team-insights-tab"; +export { TeamMemberModal } from "./team-member-modal"; diff --git a/components/admin/team-detail-client-wrapper.tsx b/components/admin/team-detail-client-wrapper.tsx index 448fcd4..8d5185f 100644 --- a/components/admin/team-detail-client-wrapper.tsx +++ b/components/admin/team-detail-client-wrapper.tsx @@ -1,39 +1,11 @@ "use client"; import React, { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - Users, - TrendingUp, - Target, - Award, - BarChart3, - ArrowLeft, - Download, - UserCheck, - GraduationCap, - Building2, - Star, - BookOpen, - X, - User, - Brain, - Coffee, - Lightbulb, - MessageSquare, -} from "lucide-react"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; import { TeamStats, TeamMember } from "@/services/admin-service"; -import { TechIcon } from "@/components/icons/tech-icon"; +import { TeamDetailHeader } from "./team-detail-header"; +import { TeamMetricsCards } from "./team-metrics-cards"; +import { TeamDetailTabs } from "./team-detail-tabs"; +import { TeamMemberModal } from "@/components/admin"; interface TeamDetailClientWrapperProps { team: TeamStats; @@ -59,37 +31,21 @@ interface SkillAnalysis { proficiencyRate: number; } -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"; +interface TeamInsights { + averageTeamLevel: number; + totalExperts: number; + totalLearners: number; + skillGaps: number; + strongSkills: number; } export function TeamDetailClientWrapper({ team, teamId, }: TeamDetailClientWrapperProps) { - const router = useRouter(); const [skillAnalysis, setSkillAnalysis] = useState([]); const [selectedMember, setSelectedMember] = useState(null); const [isMemberModalOpen, setIsMemberModalOpen] = useState(false); - const [selectedCategory, setSelectedCategory] = useState("all"); useEffect(() => { // Analyser les compétences avec les vraies données @@ -154,74 +110,14 @@ export function TeamDetailClientWrapper({ totalMembers: team.totalMembers, averageSkillLevel: team.averageSkillLevel, skillCoverage: team.skillCoverage, - expertiseDistribution: { - beginners: team.members.filter( - (m) => - m.skills.reduce((avg, s) => avg + s.level, 0) / m.skills.length < - 1 - ).length, - intermediate: team.members.filter((m) => { - const avg = - m.skills.reduce((avg, s) => avg + s.level, 0) / m.skills.length; - return avg >= 1 && avg < 2; - }).length, - advanced: team.members.filter((m) => { - const avg = - m.skills.reduce((avg, s) => avg + s.level, 0) / m.skills.length; - return avg >= 2 && avg < 3; - }).length, - experts: team.members.filter( - (m) => - m.skills.reduce((avg, s) => avg + s.level, 0) / m.skills.length >= - 3 - ).length, - }, }, members: team.members.map((member) => ({ name: `${member.firstName} ${member.lastName}`, joinDate: member.joinDate, skillCount: member.skills.length, - averageLevel: - member.skills.reduce((avg, s) => avg + s.level, 0) / - member.skills.length, - expertSkills: member.skills.filter((s) => s.level >= 2 && s.canMentor) - .length, - learningGoals: member.skills.filter((s) => s.wantsToLearn).length, skills: member.skills, })), - skillAnalysis: skillAnalysis.map((skill) => ({ - name: skill.skillName, - category: skill.category, - averageLevel: skill.averageLevel, - totalEvaluations: skill.totalEvaluations, - expertCount: skill.expertCount, - learnerCount: skill.learnerCount, - proficiencyRate: skill.proficiencyRate, - experts: skill.experts, - learners: skill.learners, - })), - insights: { - topPerformingSkills: skillAnalysis.slice(0, 5).map((s) => ({ - name: s.skillName, - level: s.averageLevel, - proficiency: s.proficiencyRate, - })), - skillsNeedingDevelopment: skillAnalysis - .filter((s) => s.averageLevel < 1.5) - .slice(0, 5) - .map((s) => ({ - name: s.skillName, - level: s.averageLevel, - learners: s.learnerCount, - })), - mentorshipOpportunities: skillAnalysis - .filter((s) => s.experts.filter((e) => e.canMentor).length > 0) - .map((s) => ({ - skill: s.skillName, - mentors: s.experts.filter((e) => e.canMentor).map((e) => e.name), - potentialMentees: s.learners.map((l) => l.name), - })), - }, + skillAnalysis, }; const dataStr = JSON.stringify(reportData, null, 2); @@ -238,13 +134,7 @@ export function TeamDetailClientWrapper({ linkElement.click(); }; - const categories = Array.from(new Set(skillAnalysis.map((s) => s.category))); - const filteredSkills = - selectedCategory === "all" - ? skillAnalysis - : skillAnalysis.filter((s) => s.category === selectedCategory); - - const teamInsights = { + const teamInsights: TeamInsights = { averageTeamLevel: team.members.reduce((sum, member) => { const memberAvg = @@ -273,905 +163,37 @@ export function TeamDetailClientWrapper({
- {/* Header avec breadcrumb et actions */} -
-
- -
|
-
-
- -
-
-

- {team.teamName} -

-

{team.direction}

-
-
-
-
- -
-
+ {/* Header */} + {/* Métriques principales */} -
-
-
-
- -
- - Membres - -
-
- {team.totalMembers} -
-
personnes actives
-
- -
-
-
- -
- - Niveau équipe - -
-
- {((teamInsights.averageTeamLevel / 3) * 100).toFixed(0)}% -
-
- {getSkillLevelLabel(teamInsights.averageTeamLevel)} -
-
- -
-
-
- -
- - Expertises - -
-
- {teamInsights.totalExperts} -
-
- compétences expertes -
-
- -
-
-
- -
- - Apprentissages - -
-
- {teamInsights.totalLearners} -
-
- objectifs d'apprentissage -
-
- -
-
-
- -
- - Lacunes - -
-
- {teamInsights.skillGaps} -
-
- compétences à renforcer -
-
-
+ {/* Contenu principal avec onglets */} - -
- - - Vue d'ensemble - - - Compétences - - - Membres - - - Insights - - -
- - {/* Onglet Vue d'ensemble */} - - {/* Top Skills avec design amélioré */} -
-

- - Top Compétences de l'équipe -

-
- {team.topSkills.slice(0, 6).map((skill, idx) => ( -
-
-
- {skill.icon && ( -
- -
- )} - - {skill.skillName} - -
-
-
-
-
- - {skill.averageLevel.toFixed(1)} - - - {getSkillLevelLabel(skill.averageLevel)} - -
-
-
-
-
-
- ))} -
-
- - {/* Distribution des niveaux */} -
-
-

- - Répartition des niveaux -

-
- {[ - { - label: "Expert", - count: team.members.filter( - (m) => - m.skills.reduce((avg, s) => avg + s.level, 0) / - m.skills.length >= - 2.5 - ).length, - color: "bg-green-500", - }, - { - label: "Avancé", - count: team.members.filter((m) => { - const avg = - m.skills.reduce((avg, s) => avg + s.level, 0) / - m.skills.length; - return avg >= 2 && avg < 2.5; - }).length, - color: "bg-blue-500", - }, - { - label: "Intermédiaire", - count: team.members.filter((m) => { - const avg = - m.skills.reduce((avg, s) => avg + s.level, 0) / - m.skills.length; - return avg >= 1 && avg < 2; - }).length, - color: "bg-orange-500", - }, - { - label: "Débutant", - count: team.members.filter( - (m) => - m.skills.reduce((avg, s) => avg + s.level, 0) / - m.skills.length < - 1 - ).length, - color: "bg-red-500", - }, - ].map((level, idx) => ( -
-
-
- - {level.label} - -
-
- - {level.count} - -
-
-
- - {((level.count / team.totalMembers) * 100).toFixed(0)} - % - -
-
- ))} -
-
- -
-

- - Métriques clés -

-
-
- - Couverture des compétences - - - {team.skillCoverage.toFixed(1)}% - -
-
- Compétences fortes - - {teamInsights.strongSkills} - -
-
- - Objectifs d'apprentissage - - - {teamInsights.totalLearners} - -
-
- Mentors disponibles - - {skillAnalysis.reduce( - (sum, skill) => - sum + skill.experts.filter((e) => e.canMentor).length, - 0 - )} - -
-
-
-
- - - {/* Onglet Compétences */} - - {/* Filtres par catégorie */} -
-
- - {categories.map((category) => ( - - ))} -
-
- - {/* Liste des compétences détaillée */} -
- {filteredSkills.map((skill, idx) => ( -
-
-
-

- {skill.skillName} -

-

{skill.category}

-
-
- - {getSkillLevelLabel(skill.averageLevel)} - -
-
- -
-
- - Niveau moyen: - - - {skill.averageLevel.toFixed(1)}/3 - -
- -
-
-
- -
-
-
- {skill.totalEvaluations} -
-
Évaluations
-
-
-
- {skill.expertCount} -
-
Experts
-
-
-
- {skill.learnerCount} -
-
Apprenants
-
-
- - {skill.experts.filter((e) => e.canMentor).length > 0 && ( -
-
- Mentors disponibles: -
-
- {skill.experts - .filter((e) => e.canMentor) - .slice(0, 2) - .map((expert, i) => ( - - {expert.name} - - ))} - {skill.experts.filter((e) => e.canMentor).length > - 2 && ( - - + - {skill.experts.filter((e) => e.canMentor).length - - 2} - - )} -
-
- )} -
-
- ))} -
- - - {/* Onglet Membres */} - -
- {team.members.map((member) => { - const memberAvg = - member.skills.reduce((sum, skill) => sum + skill.level, 0) / - member.skills.length; - const expertSkills = member.skills.filter( - (s) => s.level >= 2 && s.canMentor - ).length; - const learningGoals = member.skills.filter( - (s) => s.wantsToLearn - ).length; - - return ( -
{ - setSelectedMember(member); - setIsMemberModalOpen(true); - }} - > -
-
-
- -
-
-
- {member.firstName} {member.lastName} -
-
- {new Date(member.joinDate).toLocaleDateString()} -
-
-
-
- - {getSkillLevelLabel(memberAvg)} - -
-
- -
-
- Niveau moyen: - - {memberAvg.toFixed(1)}/3 - -
- -
-
-
- -
-
-
- {member.skills.length} -
-
Compétences
-
-
-
- {expertSkills} -
-
Expertises
-
-
-
- {learningGoals} -
-
Objectifs
-
-
- - {expertSkills > 0 && ( -
- - - Peut mentorer {expertSkills} compétence - {expertSkills > 1 ? "s" : ""} - -
- )} - - {learningGoals > 0 && ( -
- - - Souhaite apprendre {learningGoals} compétence - {learningGoals > 1 ? "s" : ""} - -
- )} -
-
- ); - })} -
- - - {/* Onglet Insights */} - -
- {/* Compétences à développer */} -
-

- - Compétences à développer -

-
- {skillAnalysis - .filter((s) => s.averageLevel < 1.5) - .slice(0, 5) - .map((skill, idx) => ( -
-
-
- {skill.skillName} -
-
- {skill.category} -
-
-
-
- {skill.averageLevel.toFixed(1)} -
-
- {skill.learnerCount} intéressés -
-
-
- ))} -
-
- - {/* Opportunités de mentorat */} -
-

- - Opportunités de mentorat -

-
- {skillAnalysis - .filter( - (s) => - s.experts.filter((e) => e.canMentor).length > 0 && - s.learnerCount > 0 - ) - .slice(0, 5) - .map((skill, idx) => ( -
-
-
- {skill.skillName} -
-
- {skill.category} -
-
-
-
- {skill.experts.filter((e) => e.canMentor).length}{" "} - mentor - {skill.experts.filter((e) => e.canMentor).length > 1 - ? "s" - : ""} -
-
- {skill.learnerCount} apprenant - {skill.learnerCount > 1 ? "s" : ""} -
-
-
- ))} -
-
-
- - {/* Recommandations */} -
-

- - Recommandations pour l'équipe -

-
-
-

- 🎯 Formations prioritaires -

-

- Organiser des formations sur{" "} - {skillAnalysis - .filter((s) => s.averageLevel < 1.5) - .slice(0, 2) - .map((s) => s.skillName) - .join(" et ")} - pour combler les lacunes identifiées. -

-
- -
-

- 🤝 Programme de mentorat -

-

- Mettre en place un système de mentorat avec{" "} - {skillAnalysis.reduce( - (sum, skill) => - sum + skill.experts.filter((e) => e.canMentor).length, - 0 - )}{" "} - mentors disponibles. -

-
- -
-

- 📈 Capitaliser sur les forces -

-

- Exploiter l'expertise en{" "} - {skillAnalysis - .filter((s) => s.averageLevel >= 2.5) - .slice(0, 2) - .map((s) => s.skillName) - .join(" et ")} - pour des projets ambitieux. -

-
- -
-

- 🔄 Plan de développement -

-

- Créer des parcours de montée en compétences personnalisés - pour {teamInsights.totalLearners} objectifs d'apprentissage. -

-
-
-
-
- + { + setSelectedMember(member); + setIsMemberModalOpen(true); + }} + /> {/* Modal détail membre */} - - - - - {selectedMember && ( -
-
- -
-
- - {selectedMember.firstName} {selectedMember.lastName} - -

- Membre depuis le{" "} - {new Date(selectedMember.joinDate).toLocaleDateString()} -

-
-
- )} - -
-
- - {selectedMember && ( -
- {/* Stats du membre */} -
-
-
- {selectedMember.skills.length} -
-
- Compétences évaluées -
-
-
-
- { - selectedMember.skills.filter( - (s) => s.level >= 2 && s.canMentor - ).length - } -
-
Peut mentorer
-
-
-
- { - selectedMember.skills.filter((s) => s.wantsToLearn) - .length - } -
-
- Souhaite apprendre -
-
-
- - {/* Compétences détaillées */} -
-

- Portfolio de compétences -

-
- {selectedMember.skills - .sort((a, b) => b.level - a.level) - .map((skill, idx) => ( -
-
-
-
-
- {skill.skillName} -
-
- {skill.category} -
-
-
- - {getSkillLevelLabel(skill.level)} - -
-
- -
-
-
- -
- {skill.canMentor && ( - - - Mentor - - )} - {skill.wantsToLearn && ( - - - Apprenant - - )} -
-
-
- ))} -
-
-
- )} - -
+ setIsMemberModalOpen(false)} + member={selectedMember} + />
); diff --git a/components/admin/team-detail-header.tsx b/components/admin/team-detail-header.tsx new file mode 100644 index 0000000..068dd92 --- /dev/null +++ b/components/admin/team-detail-header.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { ArrowLeft, Download, Users } from "lucide-react"; + +interface TeamDetailHeaderProps { + teamName: string; + direction: string; + onExport: () => void; +} + +export function TeamDetailHeader({ + teamName, + direction, + onExport, +}: TeamDetailHeaderProps) { + const router = useRouter(); + + return ( +
+
+ +
|
+
+
+ +
+
+

{teamName}

+

{direction}

+
+
+
+
+ +
+
+ ); +} diff --git a/components/admin/team-detail-tabs.tsx b/components/admin/team-detail-tabs.tsx new file mode 100644 index 0000000..03ea865 --- /dev/null +++ b/components/admin/team-detail-tabs.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { TeamOverviewTab } from "./team-overview-tab"; +import { TeamSkillsTab } from "./team-skills-tab"; +import { TeamMembersTab } from "./team-members-tab"; +import { TeamInsightsTab } from "./team-insights-tab"; +import { TeamStats, TeamMember } from "@/services/admin-service"; + +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 TeamInsights { + averageTeamLevel: number; + totalExperts: number; + totalLearners: number; + skillGaps: number; + strongSkills: number; +} + +interface TeamDetailTabsProps { + team: TeamStats; + skillAnalysis: SkillAnalysis[]; + teamInsights: TeamInsights; + onMemberClick: (member: TeamMember) => void; +} + +export function TeamDetailTabs({ + team, + skillAnalysis, + teamInsights, + onMemberClick, +}: TeamDetailTabsProps) { + return ( + +
+ + + Vue d'ensemble + + + Compétences + + + Membres + + + Insights + + +
+ + + + + + + + + + + + + + + + +
+ ); +} diff --git a/components/admin/team-insights-tab.tsx b/components/admin/team-insights-tab.tsx new file mode 100644 index 0000000..415d825 --- /dev/null +++ b/components/admin/team-insights-tab.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { TrendingUp, MessageSquare, Lightbulb } from "lucide-react"; + +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 TeamInsights { + averageTeamLevel: number; + totalExperts: number; + totalLearners: number; + skillGaps: number; + strongSkills: number; +} + +interface TeamInsightsTabProps { + skillAnalysis: SkillAnalysis[]; + teamInsights: TeamInsights; +} + +export function TeamInsightsTab({ + skillAnalysis, + teamInsights, +}: TeamInsightsTabProps) { + return ( + <> +
+ {/* Compétences à développer */} +
+

+ + Compétences à développer +

+
+ {skillAnalysis + .filter((s) => s.averageLevel < 1.5) + .slice(0, 5) + .map((skill, idx) => ( +
+
+
+ {skill.skillName} +
+
+ {skill.category} +
+
+
+
+ {skill.averageLevel.toFixed(1)} +
+
+ {skill.learnerCount} intéressés +
+
+
+ ))} +
+
+ + {/* Opportunités de mentorat */} +
+

+ + Opportunités de mentorat +

+
+ {skillAnalysis + .filter( + (s) => + s.experts.filter((e) => e.canMentor).length > 0 && + s.learnerCount > 0 + ) + .slice(0, 5) + .map((skill, idx) => ( +
+
+
+ {skill.skillName} +
+
+ {skill.category} +
+
+
+
+ {skill.experts.filter((e) => e.canMentor).length} mentor + {skill.experts.filter((e) => e.canMentor).length > 1 + ? "s" + : ""} +
+
+ {skill.learnerCount} apprenant + {skill.learnerCount > 1 ? "s" : ""} +
+
+
+ ))} +
+
+
+ + {/* Recommandations */} +
+

+ + Recommandations pour l'équipe +

+
+
+

+ 🎯 Formations prioritaires +

+

+ Organiser des formations sur{" "} + {skillAnalysis + .filter((s) => s.averageLevel < 1.5) + .slice(0, 2) + .map((s) => s.skillName) + .join(" et ")} + pour combler les lacunes identifiées. +

+
+ +
+

+ 🤝 Programme de mentorat +

+

+ Mettre en place un système de mentorat avec{" "} + {skillAnalysis.reduce( + (sum, skill) => + sum + skill.experts.filter((e) => e.canMentor).length, + 0 + )}{" "} + mentors disponibles. +

+
+ +
+

+ 📈 Capitaliser sur les forces +

+

+ Exploiter l'expertise en{" "} + {skillAnalysis + .filter((s) => s.averageLevel >= 2.5) + .slice(0, 2) + .map((s) => s.skillName) + .join(" et ")} + pour des projets ambitieux. +

+
+ +
+

+ 🔄 Plan de développement +

+

+ Créer des parcours de montée en compétences personnalisés pour{" "} + {teamInsights.totalLearners} objectifs d'apprentissage. +

+
+
+
+ + ); +} diff --git a/components/admin/team-member-modal.tsx b/components/admin/team-member-modal.tsx new file mode 100644 index 0000000..b68f6ba --- /dev/null +++ b/components/admin/team-member-modal.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { User, Award, BookOpen, X } from "lucide-react"; +import { TeamMember } from "@/services/admin-service"; + +interface TeamMemberModalProps { + isOpen: boolean; + onClose: () => void; + member: TeamMember | null; +} + +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 TeamMemberModal({ + isOpen, + onClose, + member, +}: TeamMemberModalProps) { + if (!member) return null; + + return ( + + + + +
+
+ +
+
+ + {member.firstName} {member.lastName} + +

+ Membre depuis le{" "} + {new Date(member.joinDate).toLocaleDateString()} +

+
+
+ +
+
+ +
+ {/* Stats du membre */} +
+
+
+ {member.skills.length} +
+
Compétences évaluées
+
+
+
+ { + member.skills.filter((s) => s.level >= 2 && s.canMentor) + .length + } +
+
Peut mentorer
+
+
+
+ {member.skills.filter((s) => s.wantsToLearn).length} +
+
Souhaite apprendre
+
+
+ + {/* Compétences détaillées */} +
+

+ Portfolio de compétences +

+
+ {member.skills + .sort((a, b) => b.level - a.level) + .map((skill, idx) => ( +
+
+
+
+
+ {skill.skillName} +
+
+ {skill.category} +
+
+
+ + {getSkillLevelLabel(skill.level)} + +
+
+ +
+
+
+ +
+ {skill.canMentor && ( + + + Mentor + + )} + {skill.wantsToLearn && ( + + + Apprenant + + )} +
+
+
+ ))} +
+
+
+ +
+ ); +} diff --git a/components/admin/team-members-tab.tsx b/components/admin/team-members-tab.tsx new file mode 100644 index 0000000..44c79b8 --- /dev/null +++ b/components/admin/team-members-tab.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { User, Award, BookOpen } from "lucide-react"; +import { TeamMember } from "@/services/admin-service"; + +interface TeamMembersTabProps { + members: TeamMember[]; + onMemberClick: (member: TeamMember) => void; +} + +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 TeamMembersTab({ + members, + onMemberClick, +}: TeamMembersTabProps) { + return ( +
+ {members.map((member) => { + const memberAvg = + member.skills.reduce((sum, skill) => sum + skill.level, 0) / + member.skills.length; + const expertSkills = member.skills.filter( + (s) => s.level >= 2 && s.canMentor + ).length; + const learningGoals = member.skills.filter( + (s) => s.wantsToLearn + ).length; + + return ( +
onMemberClick(member)} + > +
+
+
+ +
+
+
+ {member.firstName} {member.lastName} +
+
+ {new Date(member.joinDate).toLocaleDateString()} +
+
+
+
+ + {getSkillLevelLabel(memberAvg)} + +
+
+ +
+
+ Niveau moyen: + + {memberAvg.toFixed(1)}/3 + +
+ +
+
+
+ +
+
+
+ {member.skills.length} +
+
Compétences
+
+
+
+ {expertSkills} +
+
Expertises
+
+
+
+ {learningGoals} +
+
Objectifs
+
+
+ + {expertSkills > 0 && ( +
+ + + Peut mentorer {expertSkills} compétence + {expertSkills > 1 ? "s" : ""} + +
+ )} + + {learningGoals > 0 && ( +
+ + + Souhaite apprendre {learningGoals} compétence + {learningGoals > 1 ? "s" : ""} + +
+ )} +
+
+ ); + })} +
+ ); +} diff --git a/components/admin/team-metrics-cards.tsx b/components/admin/team-metrics-cards.tsx new file mode 100644 index 0000000..ac679ae --- /dev/null +++ b/components/admin/team-metrics-cards.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { Users, BarChart3, Award, BookOpen, Target } from "lucide-react"; + +interface TeamInsights { + averageTeamLevel: number; + totalExperts: number; + totalLearners: number; + skillGaps: number; + strongSkills: number; +} + +interface TeamMetricsCardsProps { + totalMembers: number; + teamInsights: TeamInsights; + skillCoverage: number; +} + +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"; +} + +export function TeamMetricsCards({ + totalMembers, + teamInsights, + skillCoverage, +}: TeamMetricsCardsProps) { + return ( +
+
+
+
+ +
+ Membres +
+
{totalMembers}
+
personnes actives
+
+ +
+
+
+ +
+ + Niveau équipe + +
+
+ {((teamInsights.averageTeamLevel / 3) * 100).toFixed(0)}% +
+
+ {getSkillLevelLabel(teamInsights.averageTeamLevel)} +
+
+ +
+
+
+ +
+ Expertises +
+
+ {teamInsights.totalExperts} +
+
compétences expertes
+
+ +
+
+
+ +
+ + Apprentissages + +
+
+ {teamInsights.totalLearners} +
+
+ objectifs d'apprentissage +
+
+ +
+
+
+ +
+ Lacunes +
+
+ {teamInsights.skillGaps} +
+
+ compétences à renforcer +
+
+
+ ); +} diff --git a/components/admin/team-overview-tab.tsx b/components/admin/team-overview-tab.tsx new file mode 100644 index 0000000..274997a --- /dev/null +++ b/components/admin/team-overview-tab.tsx @@ -0,0 +1,239 @@ +"use client"; + +import { Star, BarChart3, Target } from "lucide-react"; +import { TeamStats } from "@/services/admin-service"; +import { TechIcon } from "@/components/icons/tech-icon"; + +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 TeamInsights { + averageTeamLevel: number; + totalExperts: number; + totalLearners: number; + skillGaps: number; + strongSkills: number; +} + +interface TeamOverviewTabProps { + team: TeamStats; + teamInsights: TeamInsights; + skillAnalysis: SkillAnalysis[]; +} + +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"; +} + +export function TeamOverviewTab({ + team, + teamInsights, + skillAnalysis, +}: TeamOverviewTabProps) { + return ( + <> + {/* Top Skills avec design amélioré */} +
+

+ + Top Compétences de l'équipe +

+
+ {team.topSkills.slice(0, 6).map((skill, idx) => ( +
+
+
+ {skill.icon && ( +
+ +
+ )} + + {skill.skillName} + +
+
+
+
+
+ + {skill.averageLevel.toFixed(1)} + + + {skill.averageLevel < 0.5 + ? "Débutant" + : skill.averageLevel < 1.5 + ? "Intermé." + : skill.averageLevel < 2.5 + ? "Avancé" + : "Expert"} + +
+
+
+
+
+
+ ))} +
+
+ + {/* Distribution des niveaux */} +
+
+

+ + Répartition des niveaux +

+
+ {[ + { + label: "Expert", + count: team.members.filter( + (m) => + m.skills.reduce((avg, s) => avg + s.level, 0) / + m.skills.length >= + 2.5 + ).length, + color: "bg-green-500", + }, + { + label: "Avancé", + count: team.members.filter((m) => { + const avg = + m.skills.reduce((avg, s) => avg + s.level, 0) / + m.skills.length; + return avg >= 2 && avg < 2.5; + }).length, + color: "bg-blue-500", + }, + { + label: "Intermédiaire", + count: team.members.filter((m) => { + const avg = + m.skills.reduce((avg, s) => avg + s.level, 0) / + m.skills.length; + return avg >= 1 && avg < 2; + }).length, + color: "bg-orange-500", + }, + { + label: "Débutant", + count: team.members.filter( + (m) => + m.skills.reduce((avg, s) => avg + s.level, 0) / + m.skills.length < + 1 + ).length, + color: "bg-red-500", + }, + ].map((level, idx) => ( +
+
+
+ {level.label} +
+
+ {level.count} +
+
+
+ + {((level.count / team.totalMembers) * 100).toFixed(0)}% + +
+
+ ))} +
+
+ +
+

+ + Métriques clés +

+
+
+ Couverture des compétences + + {team.skillCoverage.toFixed(1)}% + +
+
+ Compétences fortes + + {teamInsights.strongSkills} + +
+
+ Objectifs d'apprentissage + + {teamInsights.totalLearners} + +
+
+ Mentors disponibles + + {skillAnalysis.reduce( + (sum, skill) => + sum + skill.experts.filter((e) => e.canMentor).length, + 0 + )} + +
+
+
+
+ + ); +} diff --git a/components/admin/team-skills-tab.tsx b/components/admin/team-skills-tab.tsx new file mode 100644 index 0000000..3038aab --- /dev/null +++ b/components/admin/team-skills-tab.tsx @@ -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("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 */} +
+
+ + {categories.map((category) => ( + + ))} +
+
+ + {/* Liste des compétences détaillée */} +
+ {filteredSkills.map((skill, idx) => ( +
+
+
+

{skill.skillName}

+

{skill.category}

+
+
+ + {getSkillLevelLabel(skill.averageLevel)} + +
+
+ +
+
+ Niveau moyen: + + {skill.averageLevel.toFixed(1)}/3 + +
+ +
+
+
+ +
+
+
+ {skill.totalEvaluations} +
+
Évaluations
+
+
+
+ {skill.expertCount} +
+
Experts
+
+
+
+ {skill.learnerCount} +
+
Apprenants
+
+
+ + {skill.experts.filter((e) => e.canMentor).length > 0 && ( +
+
+ Mentors disponibles: +
+
+ {skill.experts + .filter((e) => e.canMentor) + .slice(0, 2) + .map((expert, i) => ( + + {expert.name} + + ))} + {skill.experts.filter((e) => e.canMentor).length > 2 && ( + + +{skill.experts.filter((e) => e.canMentor).length - 2} + + )} +
+
+ )} +
+
+ ))} +
+ + ); +}