472 lines
14 KiB
TypeScript
472 lines
14 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)
|
|
let 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;
|
|
});
|
|
|
|
// Si la catégorie n'existe pas encore, la créer
|
|
if (
|
|
!updatedEvaluations.find(
|
|
(evaluation) => evaluation.category === category
|
|
)
|
|
) {
|
|
const newCategoryEvaluation: CategoryEvaluation = {
|
|
category,
|
|
selectedSkillIds: skillIds,
|
|
skills: skillIds.map((skillId) => ({
|
|
skillId,
|
|
level: "never" as SkillLevel,
|
|
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 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;
|
|
}
|