feat: add multiple skills addition and optimize evaluation handling
- Introduced `addMultipleSkillsToEvaluation` function in `EvaluationClientWrapper` for batch skill addition. - Updated `SkillEvaluation` and `SkillSelector` components to utilize the new multiple skills addition feature. - Implemented optimistic UI updates for skill level, mentor status, and learning status changes, enhancing user experience. - Refactored evaluation state management to improve performance and maintainability. - Added error handling and rollback mechanisms for better reliability during API interactions.
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUser } from "@/hooks/use-user-context";
|
||||
import { UserEvaluation, Team } from "@/lib/types";
|
||||
import { UserEvaluation, Team, SkillLevel } from "@/lib/types";
|
||||
import {
|
||||
updateSkillLevel as updateSkillLevelAction,
|
||||
updateSkillMentorStatus as updateSkillMentorStatusAction,
|
||||
@@ -24,16 +23,68 @@ export function EvaluationClientWrapper({
|
||||
children,
|
||||
}: EvaluationClientWrapperProps) {
|
||||
const { setUserInfo } = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
// Wrapper functions that refresh the page after API calls
|
||||
// É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(() => {
|
||||
setCurrentEvaluation(userEvaluation);
|
||||
}, [userEvaluation]);
|
||||
|
||||
// Fonctions avec UI optimiste
|
||||
const updateSkillLevel = async (
|
||||
category: string,
|
||||
skillId: string,
|
||||
level: any
|
||||
level: SkillLevel
|
||||
) => {
|
||||
await updateSkillLevelAction(category, skillId, level);
|
||||
router.refresh();
|
||||
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 (
|
||||
@@ -41,8 +92,39 @@ export function EvaluationClientWrapper({
|
||||
skillId: string,
|
||||
canMentor: boolean
|
||||
) => {
|
||||
await updateSkillMentorStatusAction(category, skillId, canMentor);
|
||||
router.refresh();
|
||||
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 (
|
||||
@@ -50,45 +132,204 @@ export function EvaluationClientWrapper({
|
||||
skillId: string,
|
||||
wantsToLearn: boolean
|
||||
) => {
|
||||
await updateSkillLearningStatusAction(category, skillId, wantsToLearn);
|
||||
router.refresh();
|
||||
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) => {
|
||||
await addSkillToEvaluationAction(category, skillId);
|
||||
router.refresh();
|
||||
if (!currentEvaluation) return;
|
||||
|
||||
const previousEvaluation = currentEvaluation;
|
||||
|
||||
try {
|
||||
const updatedEvaluations = currentEvaluation.evaluations.map(
|
||||
(catEval) => {
|
||||
if (catEval.category === category) {
|
||||
if (!catEval.selectedSkillIds.includes(skillId)) {
|
||||
return {
|
||||
...catEval,
|
||||
selectedSkillIds: [...catEval.selectedSkillIds, skillId],
|
||||
skills: [
|
||||
...catEval.skills,
|
||||
{
|
||||
skillId,
|
||||
level: null,
|
||||
canMentor: false,
|
||||
wantsToLearn: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
return catEval;
|
||||
}
|
||||
);
|
||||
|
||||
const newEvaluation: UserEvaluation = {
|
||||
...currentEvaluation,
|
||||
evaluations: updatedEvaluations,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
|
||||
setCurrentEvaluation(newEvaluation);
|
||||
await addSkillToEvaluationAction(category, skillId);
|
||||
} catch (error) {
|
||||
console.error("Failed to add skill to evaluation:", error);
|
||||
setCurrentEvaluation(previousEvaluation);
|
||||
}
|
||||
};
|
||||
|
||||
const addMultipleSkillsToEvaluation = async (
|
||||
category: string,
|
||||
skillIds: string[]
|
||||
) => {
|
||||
if (!currentEvaluation || skillIds.length === 0) return;
|
||||
|
||||
const previousEvaluation = currentEvaluation;
|
||||
|
||||
try {
|
||||
const updatedEvaluations = 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: null,
|
||||
canMentor: false,
|
||||
wantsToLearn: false,
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
return catEval;
|
||||
}
|
||||
);
|
||||
|
||||
const newEvaluation: UserEvaluation = {
|
||||
...currentEvaluation,
|
||||
evaluations: updatedEvaluations,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
|
||||
setCurrentEvaluation(newEvaluation);
|
||||
|
||||
// Ajouter toutes les compétences en parallèle côté API
|
||||
await Promise.all(
|
||||
skillIds.map((skillId) => addSkillToEvaluationAction(category, skillId))
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to add multiple skills to evaluation:", error);
|
||||
setCurrentEvaluation(previousEvaluation);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSkillFromEvaluation = async (
|
||||
category: string,
|
||||
skillId: string
|
||||
) => {
|
||||
await removeSkillFromEvaluationAction(category, skillId);
|
||||
router.refresh();
|
||||
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 (userEvaluation) {
|
||||
if (currentEvaluation) {
|
||||
const teamName =
|
||||
teams.find((t) => t.id === userEvaluation.profile.teamId)?.name || "";
|
||||
teams.find((t) => t.id === currentEvaluation.profile.teamId)?.name ||
|
||||
"";
|
||||
setUserInfo({
|
||||
firstName: userEvaluation.profile.firstName,
|
||||
lastName: userEvaluation.profile.lastName,
|
||||
firstName: currentEvaluation.profile.firstName,
|
||||
lastName: currentEvaluation.profile.lastName,
|
||||
teamName,
|
||||
});
|
||||
} else {
|
||||
setUserInfo(null);
|
||||
}
|
||||
}, [userEvaluation, teams, setUserInfo]);
|
||||
}, [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}
|
||||
@@ -100,6 +341,7 @@ export function EvaluationClientWrapper({
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
interface EvaluationContextType {
|
||||
currentEvaluation: UserEvaluation | null;
|
||||
updateSkillLevel: (categoryId: string, skillId: string, level: any) => void;
|
||||
updateSkillMentorStatus: (
|
||||
categoryId: string,
|
||||
@@ -112,6 +354,10 @@ interface EvaluationContextType {
|
||||
wantsToLearn: boolean
|
||||
) => void;
|
||||
addSkillToEvaluation: (categoryId: string, skillId: string) => void;
|
||||
addMultipleSkillsToEvaluation: (
|
||||
categoryId: string,
|
||||
skillIds: string[]
|
||||
) => void;
|
||||
removeSkillFromEvaluation: (categoryId: string, skillId: string) => void;
|
||||
}
|
||||
|
||||
@@ -121,10 +367,12 @@ const EvaluationContext = createContext<EvaluationContextType | undefined>(
|
||||
|
||||
function EvaluationProvider({
|
||||
children,
|
||||
currentEvaluation,
|
||||
updateSkillLevel,
|
||||
updateSkillMentorStatus,
|
||||
updateSkillLearningStatus,
|
||||
addSkillToEvaluation,
|
||||
addMultipleSkillsToEvaluation,
|
||||
removeSkillFromEvaluation,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
@@ -132,10 +380,12 @@ function EvaluationProvider({
|
||||
return (
|
||||
<EvaluationContext.Provider
|
||||
value={{
|
||||
currentEvaluation,
|
||||
updateSkillLevel,
|
||||
updateSkillMentorStatus,
|
||||
updateSkillLearningStatus,
|
||||
addSkillToEvaluation,
|
||||
addMultipleSkillsToEvaluation,
|
||||
removeSkillFromEvaluation,
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user