reafctor: pages for management and split components

This commit is contained in:
Julien Froidefond
2025-08-23 08:16:09 +02:00
parent 97d274190d
commit 2e195ca5cf
29 changed files with 1968 additions and 1607 deletions

View File

@@ -0,0 +1,199 @@
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { SkillCategory } from "@/lib/types";
import {
AdminManagementService,
Skill,
} from "@/services/admin-management-service";
interface SkillFormData {
name: string;
categoryId: string;
description: string;
icon: string;
}
export function useSkillsManagement(skillCategories: SkillCategory[]) {
const [skills, setSkills] = useState<Skill[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [editingSkill, setEditingSkill] = useState<Skill | null>(null);
const [skillFormData, setSkillFormData] = useState<SkillFormData>({
name: "",
categoryId: "",
description: "",
icon: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast();
// 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 resetForm = () => {
setSkillFormData({ name: "", categoryId: "", description: "", icon: "" });
setEditingSkill(null);
};
const handleCreateSkill = async () => {
if (!skillFormData.name || !skillFormData.categoryId) {
toast({
title: "Erreur",
description: "Veuillez remplir tous les champs obligatoires",
variant: "destructive",
});
return false;
}
try {
setIsSubmitting(true);
const categoryIndex = parseInt(skillFormData.categoryId);
const category = skillCategories[categoryIndex];
const skillData = {
...skillFormData,
category: category.category,
};
const newSkill = await AdminManagementService.createSkill(skillData);
setSkills([...skills, newSkill]);
resetForm();
toast({
title: "Succès",
description: "Skill créée avec succès",
});
return true;
} catch (error: any) {
toast({
title: "Erreur",
description: error.message || "Erreur lors de la création de la skill",
variant: "destructive",
});
return false;
} finally {
setIsSubmitting(false);
}
};
const handleEditSkill = (skill: Skill) => {
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,
});
};
const handleUpdateSkill = async () => {
if (!editingSkill || !skillFormData.name || !skillFormData.categoryId) {
toast({
title: "Erreur",
description: "Veuillez remplir tous les champs obligatoires",
variant: "destructive",
});
return false;
}
try {
setIsSubmitting(true);
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);
resetForm();
toast({
title: "Succès",
description: "Skill mise à jour avec succès",
});
return true;
} catch (error: any) {
toast({
title: "Erreur",
description:
error.message || "Erreur lors de la mise à jour de la skill",
variant: "destructive",
});
return false;
} finally {
setIsSubmitting(false);
}
};
const handleDeleteSkill = async (skillId: string) => {
if (
!confirm(
"Êtes-vous sûr de vouloir supprimer cette skill ? Cette action est irréversible."
)
) {
return;
}
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",
});
}
};
return {
skills,
isLoading,
editingSkill,
skillFormData,
isSubmitting,
setSkillFormData,
resetForm,
handleCreateSkill,
handleEditSkill,
handleUpdateSkill,
handleDeleteSkill,
};
}

View File

@@ -0,0 +1,273 @@
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { Team as TeamType } from "@/lib/types";
import { TeamStats } from "@/services/admin-service";
import {
AdminManagementService,
Team,
} from "@/services/admin-management-service";
interface TeamFormData {
name: string;
direction: string;
}
export function useTeamsManagement(
initialTeams: TeamType[],
initialTeamStats: TeamStats[]
) {
const [teams, setTeams] = useState<TeamType[]>(initialTeams);
const [teamStats, setTeamStats] = useState<TeamStats[]>(initialTeamStats);
const [editingTeam, setEditingTeam] = useState<TeamType | null>(null);
const [teamFormData, setTeamFormData] = useState<TeamFormData>({
name: "",
direction: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast();
// Charger les teams depuis l'API
const fetchTeams = async () => {
try {
const teamsData = await AdminManagementService.getTeams();
// Note: on garde les teams existantes pour la compatibilité
// Les nouvelles teams créées via l'API seront visibles après rafraîchissement
} catch (error) {
console.error("Error fetching teams:", error);
toast({
title: "Erreur",
description: "Impossible de charger les teams",
variant: "destructive",
});
}
};
// Charger les teams au montage du composant
useEffect(() => {
fetchTeams();
}, []);
const resetForm = () => {
setTeamFormData({ name: "", direction: "" });
setEditingTeam(null);
};
const getTeamStats = (teamId: string): TeamStats | undefined => {
return teamStats.find((stats) => stats.teamId === teamId);
};
const handleCreateTeam = async () => {
if (!teamFormData.name || !teamFormData.direction) {
toast({
title: "Erreur",
description: "Veuillez remplir tous les champs obligatoires",
variant: "destructive",
});
return false;
}
try {
setIsSubmitting(true);
const newTeam = await AdminManagementService.createTeam(teamFormData);
toast({
title: "Succès",
description: "Équipe créée avec succès",
});
resetForm();
// Mettre à jour l'état local avec la nouvelle équipe
const newLocalTeam: TeamType = {
id: newTeam.id,
name: newTeam.name,
direction: newTeam.direction,
};
setTeams((prev) => [...prev, newLocalTeam]);
// Ajouter les stats de la nouvelle équipe (avec les propriétés minimales)
const newTeamStats = {
teamId: newTeam.id,
teamName: newTeam.name,
direction: newTeam.direction,
totalMembers: 0,
averageSkillLevel: 0,
skillCoverage: 0,
topSkills: [],
members: [],
} as TeamStats;
setTeamStats((prev) => [...prev, newTeamStats]);
return true;
} catch (error: any) {
toast({
title: "Erreur",
description: error.message || "Erreur lors de la création de l'équipe",
variant: "destructive",
});
return false;
} finally {
setIsSubmitting(false);
}
};
const handleEditTeam = (team: TeamType) => {
setEditingTeam(team);
setTeamFormData({
name: team.name,
direction: team.direction,
});
};
const handleUpdateTeam = async () => {
if (!editingTeam || !teamFormData.name || !teamFormData.direction) {
toast({
title: "Erreur",
description: "Veuillez remplir tous les champs obligatoires",
variant: "destructive",
});
return false;
}
try {
setIsSubmitting(true);
await AdminManagementService.updateTeam({
id: editingTeam.id,
...teamFormData,
memberCount: editingTeam.memberCount || 0,
});
toast({
title: "Succès",
description: "Équipe mise à jour avec succès",
});
resetForm();
return true;
} catch (error: any) {
toast({
title: "Erreur",
description:
error.message || "Erreur lors de la mise à jour de l'équipe",
variant: "destructive",
});
return false;
} finally {
setIsSubmitting(false);
}
};
const handleDeleteTeam = async (teamId: string) => {
const team = teams.find((t) => t.id === teamId);
const stats = getTeamStats(teamId);
if (stats && stats.totalMembers > 0) {
toast({
title: "Erreur",
description:
"Impossible de supprimer une équipe qui contient des membres",
variant: "destructive",
});
return;
}
if (
confirm(
`Êtes-vous sûr de vouloir supprimer l'équipe "${team?.name}" ? Cette action est irréversible.`
)
) {
try {
await AdminManagementService.deleteTeam(teamId);
toast({
title: "Succès",
description: "Équipe supprimée avec succès",
});
// Mettre à jour l'état local au lieu de recharger la page
setTeams((prev) => prev.filter((t) => t.id !== teamId));
setTeamStats((prev) =>
prev.filter((stats) => stats.teamId !== teamId)
);
} catch (error: any) {
toast({
title: "Erreur",
description:
error.message || "Erreur lors de la suppression de l'équipe",
variant: "destructive",
});
}
}
};
const handleDeleteDirection = async (direction: string) => {
// Vérifier si des équipes de cette direction ont des membres
const teamsInDirection = teams.filter(
(team) => team.direction === direction
);
const hasMembers = teamsInDirection.some((team) => {
const stats = getTeamStats(team.id);
return stats && stats.totalMembers > 0;
});
if (hasMembers) {
toast({
title: "Erreur",
description: `Impossible de supprimer la direction "${direction}" car certaines équipes ont des membres`,
variant: "destructive",
});
return;
}
if (
confirm(
`Êtes-vous sûr de vouloir supprimer la direction "${direction}" et TOUTES ses équipes ?\n\n⚠ Cette action est irréversible !\n\nÉquipes qui seront supprimées :\n${teamsInDirection
.map((t) => `${t.name}`)
.join("\n")}`
)
) {
try {
await AdminManagementService.deleteDirection(direction);
toast({
title: "Succès",
description: `Direction "${direction}" et toutes ses équipes supprimées avec succès`,
variant: "default",
});
// Mettre à jour l'état local au lieu de recharger la page
setTeams((prev) =>
prev.filter((team) => team.direction !== direction)
);
setTeamStats((prev) =>
prev.filter((stats) => stats.direction !== direction)
);
} catch (error: any) {
toast({
title: "Erreur",
description:
error.message || "Erreur lors de la suppression de la direction",
variant: "destructive",
});
}
}
};
// Extraire les directions uniques pour les formulaires
const directions = Array.from(
new Set(teams.map((team) => team.direction))
);
return {
teams,
teamStats,
editingTeam,
teamFormData,
isSubmitting,
directions,
setTeamFormData,
resetForm,
getTeamStats,
handleCreateTeam,
handleEditTeam,
handleUpdateTeam,
handleDeleteTeam,
handleDeleteDirection,
};
}

View File

@@ -0,0 +1,150 @@
import { useState, useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
import { Team } from "@/services/admin-management-service";
interface User {
uuid: string;
firstName: string;
lastName: string;
teamName?: string;
hasEvaluations: boolean;
}
interface UserFormData {
firstName: string;
lastName: string;
teamId: string;
}
export function useUsersManagement(teams: Team[]) {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [deletingUserId, setDeletingUserId] = useState<string | null>(null);
const [userFormData, setUserFormData] = useState<UserFormData>({
firstName: "",
lastName: "",
teamId: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast();
// Charger les utilisateurs depuis l'API
const fetchUsers = async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/admin/users");
if (!response.ok) {
throw new Error("Erreur lors de la récupération des utilisateurs");
}
const usersData = await response.json();
setUsers(usersData);
} catch (err: any) {
setError(err.message || "Erreur lors du chargement des utilisateurs");
} finally {
setIsLoading(false);
}
};
// Charger les utilisateurs au montage du composant
useEffect(() => {
fetchUsers();
}, []);
const resetForm = () => {
setUserFormData({ firstName: "", lastName: "", teamId: "" });
};
const handleCreateUser = async () => {
if (!userFormData.firstName || !userFormData.lastName) {
toast({
title: "Erreur",
description: "Veuillez remplir tous les champs obligatoires",
variant: "destructive",
});
return false;
}
try {
setIsSubmitting(true);
// TODO: Implémenter la création d'utilisateur
toast({
title: "Succès",
description: "Utilisateur créé avec succès",
});
resetForm();
// Rafraîchir la liste
await fetchUsers();
return true;
} catch (error: any) {
toast({
title: "Erreur",
description:
error.message || "Erreur lors de la création de l'utilisateur",
variant: "destructive",
});
return false;
} finally {
setIsSubmitting(false);
}
};
const handleDeleteUser = async (user: User) => {
if (user.teamName) {
toast({
title: "Action impossible",
description:
"Retirez d'abord l'utilisateur de son équipe avant de le supprimer",
variant: "destructive",
});
return;
}
if (
!confirm(
`Êtes-vous sûr de vouloir supprimer définitivement ${user.firstName} ${user.lastName} ?\n\nCette action supprimera aussi toutes ses évaluations par skills et est irréversible.`
)
) {
return;
}
setDeletingUserId(user.uuid);
try {
// TODO: Implémenter la suppression d'utilisateur
// await AdminManagementService.deleteUser(user.uuid);
// Mettre à jour la liste locale
setUsers((prev) => prev.filter((u) => u.uuid !== user.uuid));
toast({
title: "Succès",
description: `${user.firstName} ${user.lastName} a été supprimé avec succès`,
});
} catch (err: any) {
toast({
title: "Erreur",
description:
err.message || "Erreur lors de la suppression de l'utilisateur",
variant: "destructive",
});
} finally {
setDeletingUserId(null);
}
};
return {
users,
isLoading,
error,
deletingUserId,
userFormData,
isSubmitting,
setUserFormData,
resetForm,
handleCreateUser,
handleDeleteUser,
fetchUsers,
};
}