From 2e195ca5cf800857c93758d10b1684fcef5c8a1b Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sat, 23 Aug 2025 08:16:09 +0200 Subject: [PATCH] reafctor: pages for management and split components --- app/admin/manage/layout.tsx | 28 + app/admin/manage/page.tsx | 28 +- app/admin/manage/skills/page.tsx | 37 ++ app/admin/manage/teams/page.tsx | 38 ++ app/admin/manage/users/page.tsx | 36 ++ app/api/admin/skills/route.ts | 3 + components/admin/layout/index.ts | 3 +- .../layout/manage-admin-client-wrapper.tsx | 43 -- .../admin/layout/manage-content-tabs.tsx | 69 --- components/admin/layout/manage-navigation.tsx | 58 ++ components/admin/management/pages/index.ts | 4 +- .../management/pages/skills-management.tsx | 518 ---------------- .../management/pages/teams-management.tsx | 565 ------------------ .../management/pages/users-management.tsx | 381 ------------ components/admin/skills/index.ts | 3 + components/admin/skills/skill-form-dialog.tsx | 123 ++++ components/admin/skills/skills-list.tsx | 116 ++++ .../admin/skills/skills-management-page.tsx | 158 +++++ components/admin/teams/index.ts | 3 + components/admin/teams/team-form-dialog.tsx | 96 +++ components/admin/teams/teams-list.tsx | 97 +++ .../admin/teams/teams-management-page.tsx | 189 ++++++ components/admin/users/index.ts | 3 + components/admin/users/user-form-dialog.tsx | 108 ++++ components/admin/users/users-list.tsx | 79 +++ .../admin/users/users-management-page.tsx | 167 ++++++ hooks/use-skills-management.ts | 199 ++++++ hooks/use-teams-management.ts | 273 +++++++++ hooks/use-users-management.ts | 150 +++++ 29 files changed, 1968 insertions(+), 1607 deletions(-) create mode 100644 app/admin/manage/layout.tsx create mode 100644 app/admin/manage/skills/page.tsx create mode 100644 app/admin/manage/teams/page.tsx create mode 100644 app/admin/manage/users/page.tsx delete mode 100644 components/admin/layout/manage-admin-client-wrapper.tsx delete mode 100644 components/admin/layout/manage-content-tabs.tsx create mode 100644 components/admin/layout/manage-navigation.tsx delete mode 100644 components/admin/management/pages/skills-management.tsx delete mode 100644 components/admin/management/pages/teams-management.tsx delete mode 100644 components/admin/management/pages/users-management.tsx create mode 100644 components/admin/skills/index.ts create mode 100644 components/admin/skills/skill-form-dialog.tsx create mode 100644 components/admin/skills/skills-list.tsx create mode 100644 components/admin/skills/skills-management-page.tsx create mode 100644 components/admin/teams/index.ts create mode 100644 components/admin/teams/team-form-dialog.tsx create mode 100644 components/admin/teams/teams-list.tsx create mode 100644 components/admin/teams/teams-management-page.tsx create mode 100644 components/admin/users/index.ts create mode 100644 components/admin/users/user-form-dialog.tsx create mode 100644 components/admin/users/users-list.tsx create mode 100644 components/admin/users/users-management-page.tsx create mode 100644 hooks/use-skills-management.ts create mode 100644 hooks/use-teams-management.ts create mode 100644 hooks/use-users-management.ts diff --git a/app/admin/manage/layout.tsx b/app/admin/manage/layout.tsx new file mode 100644 index 0000000..82ac20a --- /dev/null +++ b/app/admin/manage/layout.tsx @@ -0,0 +1,28 @@ +import { AdminHeader } from "@/components/admin/utils/admin-header"; +import { ManageNavigation } from "@/components/admin/layout/manage-navigation"; + +export default function ManageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {/* Background Effects */} +
+
+
+ +
+ {/* Header */} + + + {/* Navigation */} + + + {/* Main Content */} + {children} +
+
+ ); +} diff --git a/app/admin/manage/page.tsx b/app/admin/manage/page.tsx index 4d57a8b..316948b 100644 --- a/app/admin/manage/page.tsx +++ b/app/admin/manage/page.tsx @@ -1,7 +1,5 @@ import { redirect } from "next/navigation"; import { isUserAuthenticated } from "@/lib/server-auth"; -import { AdminService } from "@/services/admin-service"; -import { ManageAdminClientWrapper } from "@/components/admin"; export default async function ManageAdminPage() { // Vérifier l'authentification @@ -12,28 +10,6 @@ export default async function ManageAdminPage() { redirect("/login"); } - // Charger les données côté serveur - try { - const adminData = await AdminService.getAdminData(); - - return ( - - ); - } catch (error) { - console.error("Failed to load admin data:", error); - return ( -
-
-
- Erreur lors du chargement des données d'administration -
-
-
- ); - } + // Rediriger vers la page skills par défaut + redirect("/admin/manage/skills"); } diff --git a/app/admin/manage/skills/page.tsx b/app/admin/manage/skills/page.tsx new file mode 100644 index 0000000..dff7fdf --- /dev/null +++ b/app/admin/manage/skills/page.tsx @@ -0,0 +1,37 @@ +import { redirect } from "next/navigation"; +import { isUserAuthenticated } from "@/lib/server-auth"; +import { AdminService } from "@/services/admin-service"; +import { SkillsManagementPage } from "@/components/admin/skills"; + +export default async function SkillsPage() { + // Vérifier l'authentification + const isAuthenticated = await isUserAuthenticated(); + + // Si pas de cookie d'authentification, rediriger vers login + if (!isAuthenticated) { + redirect("/login"); + } + + // Charger les données côté serveur + try { + const adminData = await AdminService.getAdminData(); + + return ( + + ); + } catch (error) { + console.error("Failed to load admin data:", error); + return ( +
+
+
+ Erreur lors du chargement des données d'administration +
+
+
+ ); + } +} diff --git a/app/admin/manage/teams/page.tsx b/app/admin/manage/teams/page.tsx new file mode 100644 index 0000000..20caf95 --- /dev/null +++ b/app/admin/manage/teams/page.tsx @@ -0,0 +1,38 @@ +import { redirect } from "next/navigation"; +import { isUserAuthenticated } from "@/lib/server-auth"; +import { AdminService } from "@/services/admin-service"; +import { TeamsManagementPage } from "@/components/admin/teams"; + +export default async function TeamsPage() { + // Vérifier l'authentification + const isAuthenticated = await isUserAuthenticated(); + + // Si pas de cookie d'authentification, rediriger vers login + if (!isAuthenticated) { + redirect("/login"); + } + + // Charger les données côté serveur + try { + const adminData = await AdminService.getAdminData(); + + return ( + + ); + } catch (error) { + console.error("Failed to load admin data:", error); + return ( +
+
+
+ Erreur lors du chargement des données d'administration +
+
+
+ ); + } +} diff --git a/app/admin/manage/users/page.tsx b/app/admin/manage/users/page.tsx new file mode 100644 index 0000000..b7980b6 --- /dev/null +++ b/app/admin/manage/users/page.tsx @@ -0,0 +1,36 @@ +import { redirect } from "next/navigation"; +import { isUserAuthenticated } from "@/lib/server-auth"; +import { AdminService } from "@/services/admin-service"; +import { UsersManagementPage } from "@/components/admin/users"; + +export default async function UsersPage() { + // Vérifier l'authentification + const isAuthenticated = await isUserAuthenticated(); + + // Si pas de cookie d'authentification, rediriger vers login + if (!isAuthenticated) { + redirect("/login"); + } + + // Charger les données côté serveur + try { + const adminData = await AdminService.getAdminData(); + + return ( + + ); + } catch (error) { + console.error("Failed to load admin data:", error); + return ( +
+
+
+ Erreur lors du chargement des données d'administration +
+
+
+ ); + } +} diff --git a/app/api/admin/skills/route.ts b/app/api/admin/skills/route.ts index 99a1fc6..1541583 100644 --- a/app/api/admin/skills/route.ts +++ b/app/api/admin/skills/route.ts @@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from "next/server"; import { getPool } from "@/services/database"; import { isUserAuthenticated } from "@/lib/server-auth"; +// Configuration pour éviter la génération statique +export const dynamic = "force-dynamic"; + // GET - Récupérer toutes les skills export async function GET() { try { diff --git a/components/admin/layout/index.ts b/components/admin/layout/index.ts index 32d842b..2d849c7 100644 --- a/components/admin/layout/index.ts +++ b/components/admin/layout/index.ts @@ -1,3 +1,2 @@ // Composants de layout et navigation -export { ManageAdminClientWrapper } from "./manage-admin-client-wrapper"; -export { ManageContentTabs } from "./manage-content-tabs"; +export { ManageNavigation } from "./manage-navigation"; diff --git a/components/admin/layout/manage-admin-client-wrapper.tsx b/components/admin/layout/manage-admin-client-wrapper.tsx deleted file mode 100644 index a11b463..0000000 --- a/components/admin/layout/manage-admin-client-wrapper.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Team, SkillCategory } from "@/lib/types"; -import { TeamStats, DirectionStats } from "@/services/admin-service"; -import { AdminHeader } from "../utils/admin-header"; -import { ManageContentTabs } from "./manage-content-tabs"; - -interface ManageAdminClientWrapperProps { - teams: Team[]; - skillCategories: SkillCategory[]; - teamStats: TeamStats[]; - directionStats: DirectionStats[]; -} - -export function ManageAdminClientWrapper({ - teams, - skillCategories, - teamStats, - directionStats, -}: ManageAdminClientWrapperProps) { - return ( -
- {/* Background Effects */} -
-
-
- -
- {/* Header */} - - - {/* Main Content Tabs */} - -
-
- ); -} diff --git a/components/admin/layout/manage-content-tabs.tsx b/components/admin/layout/manage-content-tabs.tsx deleted file mode 100644 index 41f8a8a..0000000 --- a/components/admin/layout/manage-content-tabs.tsx +++ /dev/null @@ -1,69 +0,0 @@ -"use client"; - -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Code2, Users, User } from "lucide-react"; -import { Team, SkillCategory } from "@/lib/types"; -import { TeamStats, DirectionStats } from "@/services/admin-service"; -import { SkillsManagement } from "../management/pages/skills-management"; -import { TeamsManagement } from "../management/pages/teams-management"; -import { UsersManagement } from "../management/pages/users-management"; - -interface ManageContentTabsProps { - teams: Team[]; - skillCategories: SkillCategory[]; - teamStats: TeamStats[]; - directionStats: DirectionStats[]; -} - -export function ManageContentTabs({ - teams, - skillCategories, - teamStats, - directionStats, -}: ManageContentTabsProps) { - return ( - -
- - - - Gestion des Skills - - - - Gestion des Teams - - - - Gestion des Utilisateurs - - -
- - - - - - - - - - - - -
- ); -} diff --git a/components/admin/layout/manage-navigation.tsx b/components/admin/layout/manage-navigation.tsx new file mode 100644 index 0000000..703abad --- /dev/null +++ b/components/admin/layout/manage-navigation.tsx @@ -0,0 +1,58 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Code2, Users, User } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export function ManageNavigation() { + const pathname = usePathname(); + + const navItems = [ + { + href: "/admin/manage/skills", + label: "Gestion des Skills", + icon: Code2, + value: "skills", + }, + { + href: "/admin/manage/teams", + label: "Gestion des Teams", + icon: Users, + value: "teams", + }, + { + href: "/admin/manage/users", + label: "Gestion des Utilisateurs", + icon: User, + value: "users", + }, + ]; + + return ( +
+
+ {navItems.map((item) => { + const Icon = item.icon; + const isActive = pathname === item.href; + + return ( + + + {item.label} + + ); + })} +
+
+ ); +} diff --git a/components/admin/management/pages/index.ts b/components/admin/management/pages/index.ts index 4488e85..e9df42a 100644 --- a/components/admin/management/pages/index.ts +++ b/components/admin/management/pages/index.ts @@ -1,4 +1,2 @@ // Composants de pages de gestion -export { SkillsManagement } from "./skills-management"; -export { TeamsManagement } from "./teams-management"; -export { UsersManagement } from "./users-management"; +// Les composants sont maintenant organisés dans leurs dossiers respectifs diff --git a/components/admin/management/pages/skills-management.tsx b/components/admin/management/pages/skills-management.tsx deleted file mode 100644 index c5af58f..0000000 --- a/components/admin/management/pages/skills-management.tsx +++ /dev/null @@ -1,518 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { - Plus, - Edit, - Trash2, - Code2, - Palette, - Database, - Cloud, - Shield, - Smartphone, - Layers, -} from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { useToast } from "@/hooks/use-toast"; -import { SkillCategory, Team } from "@/lib/types"; -import { - AdminManagementService, - Skill, -} from "@/services/admin-management-service"; -import { TechIcon } from "@/components/icons/tech-icon"; -import { - TreeCategoryHeader, - TreeItemRow, -} from "@/components/admin"; -import { TreeViewPage } from "../tree-view-page"; -import { useTreeView } from "@/hooks/use-tree-view"; -import { useFormDialog } from "@/hooks/use-form-dialog"; - -interface SkillsManagementProps { - skillCategories: SkillCategory[]; - teams: Team[]; -} - -interface SkillFormData { - name: string; - categoryId: string; - description: string; - icon: string; -} - -export function SkillsManagement({ - skillCategories, - teams, -}: SkillsManagementProps) { - const [searchTerm, setSearchTerm] = useState(""); - const [editingSkill, setEditingSkill] = useState(null); - const [skillFormData, setSkillFormData] = useState({ - name: "", - categoryId: "", - description: "", - icon: "", - }); - const { toast } = useToast(); - const { isCreateDialogOpen, isEditDialogOpen, openCreateDialog, closeCreateDialog, openEditDialog, closeEditDialog } = useFormDialog(); - - // État des skills - const [skills, setSkills] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - // Utilisation du hook factorisé - const { - filteredDataByCategory: filteredSkillsByCategory, - expandedCategories, - toggleCategory, - expandAll, - collapseAll, - } = useTreeView({ - data: skills, - searchFields: ['name', 'description'], - groupBy: (skill) => skill.category, - searchTerm, - onSearchChange: setSearchTerm, - }); - - // Charger les skills depuis l'API - const fetchSkills = async () => { - try { - setIsLoading(true); - const skillsData = await AdminManagementService.getSkills(); - setSkills(skillsData); - } catch (error) { - console.error("Error fetching skills:", error); - toast({ - title: "Erreur", - description: "Impossible de charger les skills", - variant: "destructive", - }); - } finally { - setIsLoading(false); - } - }; - - // Charger les skills au montage du composant - useEffect(() => { - fetchSkills(); - }, []); - - const handleCreateSkill = async () => { - if (!skillFormData.name || !skillFormData.categoryId) { - toast({ - title: "Erreur", - description: "Veuillez remplir tous les champs obligatoires", - variant: "destructive", - }); - return; - } - - try { - const categoryIndex = parseInt(skillFormData.categoryId); - const category = skillCategories[categoryIndex]; - - const skillData = { - ...skillFormData, - category: category.category, - }; - - const newSkill = await AdminManagementService.createSkill(skillData); - setSkills([...skills, newSkill]); - setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); - closeCreateDialog(); - - toast({ - title: "Succès", - description: "Skill créée avec succès", - }); - } catch (error: any) { - toast({ - title: "Erreur", - description: error.message || "Erreur lors de la création de la skill", - variant: "destructive", - }); - } - }; - - const handleEditSkill = (skill: any) => { - setEditingSkill(skill); - const categoryIndex = skillCategories.findIndex( - (cat) => cat.category === skill.category - ); - setSkillFormData({ - name: skill.name, - categoryId: categoryIndex !== -1 ? categoryIndex.toString() : "", - description: skill.description, - icon: skill.icon, - }); - openEditDialog(); - }; - - const handleUpdateSkill = async () => { - if (!editingSkill || !skillFormData.name || !skillFormData.categoryId) { - toast({ - title: "Erreur", - description: "Veuillez remplir tous les champs obligatoires", - variant: "destructive", - }); - return; - } - - try { - const categoryIndex = parseInt(skillFormData.categoryId); - const category = skillCategories[categoryIndex]; - - const skillData = { - id: editingSkill.id, - ...skillFormData, - category: category.category, - usageCount: editingSkill.usageCount, - }; - - const updatedSkill = await AdminManagementService.updateSkill(skillData); - - const updatedSkills = skills.map((skill) => - skill.id === editingSkill.id ? updatedSkill : skill - ); - - setSkills(updatedSkills); - closeEditDialog(); - setEditingSkill(null); - setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); - - toast({ - title: "Succès", - description: "Skill mise à jour avec succès", - }); - } catch (error: any) { - toast({ - title: "Erreur", - description: - error.message || "Erreur lors de la mise à jour de la skill", - variant: "destructive", - }); - } - }; - - const handleDeleteSkill = async (skillId: string) => { - if ( - confirm( - "Êtes-vous sûr de vouloir supprimer cette skill ? Cette action est irréversible." - ) - ) { - try { - await AdminManagementService.deleteSkill(skillId); - setSkills(skills.filter((s) => s.id !== skillId)); - toast({ - title: "Succès", - description: "Skill supprimée avec succès", - }); - } catch (error: any) { - toast({ - title: "Erreur", - description: - error.message || "Erreur lors de la suppression de la skill", - variant: "destructive", - }); - } - } - }; - - const resetForm = () => { - setSkillFormData({ name: "", categoryId: "", description: "", icon: "" }); - }; - - // Fonction pour obtenir l'icône de la catégorie - const getCategoryIcon = (category: string) => { - const categoryName = category.toLowerCase(); - if (categoryName.includes("frontend") || categoryName.includes("front")) - return Code2; - if (categoryName.includes("backend") || categoryName.includes("back")) - return Layers; - if ( - categoryName.includes("design") || - categoryName.includes("ui") || - categoryName.includes("ux") - ) - return Palette; - if (categoryName.includes("data") || categoryName.includes("database")) - return Database; - if (categoryName.includes("cloud") || categoryName.includes("devops")) - return Cloud; - if (categoryName.includes("security") || categoryName.includes("securité")) - return Shield; - if ( - categoryName.includes("mobile") || - categoryName.includes("android") || - categoryName.includes("ios") - ) - return Smartphone; - return Code2; // Par défaut - }; - - const headerActions = ( - - - - - - - Créer une nouvelle skill - -
-
- - - setSkillFormData({ ...skillFormData, name: e.target.value }) - } - placeholder="Ex: React, Node.js, PostgreSQL" - /> -
-
- - -
-
- -