Files
peakskills/components/evaluation/client-wrapper.tsx
2025-08-22 12:03:59 +02:00

453 lines
13 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useUser } from "@/hooks/use-user-context";
import {
UserEvaluation,
Team,
SkillLevel,
UserProfile,
CategoryEvaluation,
} from "@/lib/types";
import {
updateSkillLevel as updateSkillLevelAction,
updateSkillMentorStatus as updateSkillMentorStatusAction,
updateSkillLearningStatus as updateSkillLearningStatusAction,
addSkillToEvaluation as addSkillToEvaluationAction,
removeSkillFromEvaluation as removeSkillFromEvaluationAction,
} from "@/lib/evaluation-actions";
import { createEmptyEvaluation } from "@/lib/evaluation-utils";
interface EvaluationClientWrapperProps {
userEvaluation: UserEvaluation | null;
teams: Team[];
children: React.ReactNode;
skillCategories?: any[]; // Ajouter les catégories pour créer une évaluation vide
}
export function EvaluationClientWrapper({
userEvaluation,
teams,
children,
skillCategories = [],
}: EvaluationClientWrapperProps) {
const { userInfo, setUserInfo } = useUser();
// État local pour l'UI optimiste - commence avec les données SSR
const [currentEvaluation, setCurrentEvaluation] =
useState<UserEvaluation | null>(userEvaluation);
// Met à jour l'état local quand les props changent (SSR)
useEffect(() => {
if (userEvaluation) {
setCurrentEvaluation(userEvaluation);
} else if (skillCategories.length > 0) {
// Créer une évaluation vide si aucune n'existe, pour que l'UI puisse fonctionner
const emptyEvaluation: UserEvaluation = {
profile: { firstName: "", lastName: "", teamId: "" }, // Profile temporaire
evaluations: createEmptyEvaluation(skillCategories),
lastUpdated: new Date().toISOString(),
};
setCurrentEvaluation(emptyEvaluation);
}
}, [userEvaluation, skillCategories]);
// Fonctions avec UI optimiste
const updateSkillLevel = async (
category: string,
skillId: string,
level: SkillLevel
) => {
if (!currentEvaluation) return;
// Sauvegarder l'état actuel pour le rollback
const previousEvaluation = currentEvaluation;
try {
// Optimistic UI update - mettre à jour immédiatement l'interface
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
if (catEval.category === category) {
const existingSkill = catEval.skills.find(
(s) => s.skillId === skillId
);
const updatedSkills = existingSkill
? catEval.skills.map((skill) =>
skill.skillId === skillId ? { ...skill, level } : skill
)
: [
...catEval.skills,
{ skillId, level, canMentor: false, wantsToLearn: false },
];
return {
...catEval,
skills: updatedSkills,
};
}
return catEval;
}
);
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
setCurrentEvaluation(newEvaluation);
// Appel API en arrière-plan
await updateSkillLevelAction(category, skillId, level);
} catch (error) {
console.error("Failed to update skill level:", error);
// Rollback optimiste en cas d'erreur
setCurrentEvaluation(previousEvaluation);
}
};
const updateSkillMentorStatus = async (
category: string,
skillId: string,
canMentor: boolean
) => {
if (!currentEvaluation) return;
const previousEvaluation = currentEvaluation;
try {
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
if (catEval.category === category) {
const updatedSkills = catEval.skills.map((skill) =>
skill.skillId === skillId ? { ...skill, canMentor } : skill
);
return {
...catEval,
skills: updatedSkills,
};
}
return catEval;
}
);
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
setCurrentEvaluation(newEvaluation);
await updateSkillMentorStatusAction(category, skillId, canMentor);
} catch (error) {
console.error("Failed to update skill mentor status:", error);
setCurrentEvaluation(previousEvaluation);
}
};
const updateSkillLearningStatus = async (
category: string,
skillId: string,
wantsToLearn: boolean
) => {
if (!currentEvaluation) return;
const previousEvaluation = currentEvaluation;
try {
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
if (catEval.category === category) {
const updatedSkills = catEval.skills.map((skill) =>
skill.skillId === skillId ? { ...skill, wantsToLearn } : skill
);
return {
...catEval,
skills: updatedSkills,
};
}
return catEval;
}
);
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
setCurrentEvaluation(newEvaluation);
await updateSkillLearningStatusAction(category, skillId, wantsToLearn);
} catch (error) {
console.error("Failed to update skill learning status:", error);
setCurrentEvaluation(previousEvaluation);
}
};
const addSkillToEvaluation = async (category: string, skillId: string) => {
if (!currentEvaluation) return;
const previousEvaluation = currentEvaluation;
try {
// 1. Mise à jour optimiste de l'UI (immédiate)
let updatedEvaluations: CategoryEvaluation[] =
currentEvaluation.evaluations.map((catEval) => {
if (catEval.category === category) {
// Si la compétence n'est pas déjà ajoutée
if (!catEval.selectedSkillIds.includes(skillId)) {
return {
...catEval,
selectedSkillIds: [...catEval.selectedSkillIds, skillId],
skills: [
...catEval.skills,
{
skillId,
level: "never" as SkillLevel,
canMentor: false,
wantsToLearn: false,
},
],
};
}
}
return catEval;
});
// Si la catégorie n'existe pas encore, la créer
if (
!updatedEvaluations.find(
(evaluation) => evaluation.category === category
)
) {
const newCategoryEvaluation: CategoryEvaluation = {
category,
selectedSkillIds: [skillId],
skills: [
{
skillId,
level: "never",
canMentor: false,
wantsToLearn: false,
},
],
};
updatedEvaluations = [...updatedEvaluations, newCategoryEvaluation];
}
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
// 2. Mettre à jour l'état local immédiatement
setCurrentEvaluation(newEvaluation);
// 3. Persister côté serveur (en arrière-plan)
await addSkillToEvaluationAction(category, skillId);
} catch (error) {
console.error("❌ Failed to add skill:", error);
// En cas d'erreur, rollback l'état local
setCurrentEvaluation(previousEvaluation);
}
};
const addMultipleSkillsToEvaluation = async (
category: string,
skillIds: string[]
) => {
if (!currentEvaluation || skillIds.length === 0) return;
const previousEvaluation = currentEvaluation;
try {
// 1. Mise à jour optimiste de l'UI (immédiate)
const updatedEvaluations: CategoryEvaluation[] =
currentEvaluation.evaluations.map((catEval) => {
if (catEval.category === category) {
// Filtrer seulement les compétences qui ne sont pas déjà sélectionnées
const newSkillIds = skillIds.filter(
(skillId) => !catEval.selectedSkillIds.includes(skillId)
);
if (newSkillIds.length > 0) {
return {
...catEval,
selectedSkillIds: [...catEval.selectedSkillIds, ...newSkillIds],
skills: [
...catEval.skills,
...newSkillIds.map((skillId) => ({
skillId,
level: "never" as SkillLevel,
canMentor: false,
wantsToLearn: false,
})),
],
};
}
}
return catEval;
});
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
// 2. Mettre à jour l'état local immédiatement
setCurrentEvaluation(newEvaluation);
// 3. Persister côté serveur (en arrière-plan)
await Promise.all(
skillIds.map((skillId) => addSkillToEvaluationAction(category, skillId))
);
} catch (error) {
console.error("❌ Failed to add multiple skills:", error);
// En cas d'erreur, rollback l'état local
setCurrentEvaluation(previousEvaluation);
}
};
const removeSkillFromEvaluation = async (
category: string,
skillId: string
) => {
if (!currentEvaluation) return;
const previousEvaluation = currentEvaluation;
try {
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
if (catEval.category === category) {
return {
...catEval,
selectedSkillIds: catEval.selectedSkillIds.filter(
(id) => id !== skillId
),
skills: catEval.skills.filter(
(skill) => skill.skillId !== skillId
),
};
}
return catEval;
}
);
const newEvaluation: UserEvaluation = {
...currentEvaluation,
evaluations: updatedEvaluations,
lastUpdated: new Date().toISOString(),
};
setCurrentEvaluation(newEvaluation);
await removeSkillFromEvaluationAction(category, skillId);
} catch (error) {
console.error("Failed to remove skill from evaluation:", error);
setCurrentEvaluation(previousEvaluation);
}
};
// Update user info in navigation when user evaluation is loaded
useEffect(() => {
if (currentEvaluation) {
const teamName =
teams.find((t) => t.id === currentEvaluation.profile.teamId)?.name ||
"";
setUserInfo({
firstName: currentEvaluation.profile.firstName,
lastName: currentEvaluation.profile.lastName,
teamName,
});
} else {
setUserInfo(null);
}
}, [currentEvaluation, teams, setUserInfo]);
// Provide evaluation functions to children through React context or props
return (
<EvaluationProvider
currentEvaluation={currentEvaluation}
updateSkillLevel={updateSkillLevel}
updateSkillMentorStatus={updateSkillMentorStatus}
updateSkillLearningStatus={updateSkillLearningStatus}
addSkillToEvaluation={addSkillToEvaluation}
addMultipleSkillsToEvaluation={addMultipleSkillsToEvaluation}
removeSkillFromEvaluation={removeSkillFromEvaluation}
>
{children}
</EvaluationProvider>
);
}
// Simple context provider for evaluation functions
import { createContext, useContext } from "react";
interface EvaluationContextType {
currentEvaluation: UserEvaluation | null;
updateSkillLevel: (categoryId: string, skillId: string, level: any) => void;
updateSkillMentorStatus: (
categoryId: string,
skillId: string,
canMentor: boolean
) => void;
updateSkillLearningStatus: (
categoryId: string,
skillId: string,
wantsToLearn: boolean
) => void;
addSkillToEvaluation: (categoryId: string, skillId: string) => void;
addMultipleSkillsToEvaluation: (
categoryId: string,
skillIds: string[]
) => void;
removeSkillFromEvaluation: (categoryId: string, skillId: string) => void;
}
const EvaluationContext = createContext<EvaluationContextType | undefined>(
undefined
);
function EvaluationProvider({
children,
currentEvaluation,
updateSkillLevel,
updateSkillMentorStatus,
updateSkillLearningStatus,
addSkillToEvaluation,
addMultipleSkillsToEvaluation,
removeSkillFromEvaluation,
}: {
children: React.ReactNode;
} & EvaluationContextType) {
return (
<EvaluationContext.Provider
value={{
currentEvaluation,
updateSkillLevel,
updateSkillMentorStatus,
updateSkillLearningStatus,
addSkillToEvaluation,
addMultipleSkillsToEvaluation,
removeSkillFromEvaluation,
}}
>
{children}
</EvaluationContext.Provider>
);
}
export function useEvaluationContext() {
const context = useContext(EvaluationContext);
if (context === undefined) {
throw new Error(
"useEvaluationContext must be used within an EvaluationProvider"
);
}
return context;
}