fix: evaluation on empty eval category was KO

This commit is contained in:
Julien Froidefond
2025-08-22 12:03:59 +02:00
parent 376012fce6
commit 76015510f3
5 changed files with 88 additions and 31 deletions

View File

@@ -25,7 +25,11 @@ export default async function EvaluationPage() {
]); ]);
return ( return (
<EvaluationClientWrapper userEvaluation={userEvaluation} teams={teams}> <EvaluationClientWrapper
userEvaluation={userEvaluation}
teams={teams}
skillCategories={skillCategories}
>
<div> <div>
{/* Skill Evaluation */} {/* Skill Evaluation */}
<SkillEvaluation <SkillEvaluation

View File

@@ -2,7 +2,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useUser } from "@/hooks/use-user-context"; import { useUser } from "@/hooks/use-user-context";
import { UserEvaluation, Team, SkillLevel } from "@/lib/types"; import {
UserEvaluation,
Team,
SkillLevel,
UserProfile,
CategoryEvaluation,
} from "@/lib/types";
import { import {
updateSkillLevel as updateSkillLevelAction, updateSkillLevel as updateSkillLevelAction,
updateSkillMentorStatus as updateSkillMentorStatusAction, updateSkillMentorStatus as updateSkillMentorStatusAction,
@@ -10,19 +16,22 @@ import {
addSkillToEvaluation as addSkillToEvaluationAction, addSkillToEvaluation as addSkillToEvaluationAction,
removeSkillFromEvaluation as removeSkillFromEvaluationAction, removeSkillFromEvaluation as removeSkillFromEvaluationAction,
} from "@/lib/evaluation-actions"; } from "@/lib/evaluation-actions";
import { createEmptyEvaluation } from "@/lib/evaluation-utils";
interface EvaluationClientWrapperProps { interface EvaluationClientWrapperProps {
userEvaluation: UserEvaluation | null; userEvaluation: UserEvaluation | null;
teams: Team[]; teams: Team[];
children: React.ReactNode; children: React.ReactNode;
skillCategories?: any[]; // Ajouter les catégories pour créer une évaluation vide
} }
export function EvaluationClientWrapper({ export function EvaluationClientWrapper({
userEvaluation, userEvaluation,
teams, teams,
children, children,
skillCategories = [],
}: EvaluationClientWrapperProps) { }: EvaluationClientWrapperProps) {
const { setUserInfo } = useUser(); const { userInfo, setUserInfo } = useUser();
// État local pour l'UI optimiste - commence avec les données SSR // État local pour l'UI optimiste - commence avec les données SSR
const [currentEvaluation, setCurrentEvaluation] = const [currentEvaluation, setCurrentEvaluation] =
@@ -30,8 +39,18 @@ export function EvaluationClientWrapper({
// Met à jour l'état local quand les props changent (SSR) // Met à jour l'état local quand les props changent (SSR)
useEffect(() => { useEffect(() => {
setCurrentEvaluation(userEvaluation); if (userEvaluation) {
}, [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 // Fonctions avec UI optimiste
const updateSkillLevel = async ( const updateSkillLevel = async (
@@ -173,9 +192,11 @@ export function EvaluationClientWrapper({
const previousEvaluation = currentEvaluation; const previousEvaluation = currentEvaluation;
try { try {
const updatedEvaluations = currentEvaluation.evaluations.map( // 1. Mise à jour optimiste de l'UI (immédiate)
(catEval) => { let updatedEvaluations: CategoryEvaluation[] =
currentEvaluation.evaluations.map((catEval) => {
if (catEval.category === category) { if (catEval.category === category) {
// Si la compétence n'est pas déjà ajoutée
if (!catEval.selectedSkillIds.includes(skillId)) { if (!catEval.selectedSkillIds.includes(skillId)) {
return { return {
...catEval, ...catEval,
@@ -184,7 +205,7 @@ export function EvaluationClientWrapper({
...catEval.skills, ...catEval.skills,
{ {
skillId, skillId,
level: null, level: "never" as SkillLevel,
canMentor: false, canMentor: false,
wantsToLearn: false, wantsToLearn: false,
}, },
@@ -193,8 +214,28 @@ export function EvaluationClientWrapper({
} }
} }
return catEval; 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 = { const newEvaluation: UserEvaluation = {
...currentEvaluation, ...currentEvaluation,
@@ -202,10 +243,14 @@ export function EvaluationClientWrapper({
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),
}; };
// 2. Mettre à jour l'état local immédiatement
setCurrentEvaluation(newEvaluation); setCurrentEvaluation(newEvaluation);
// 3. Persister côté serveur (en arrière-plan)
await addSkillToEvaluationAction(category, skillId); await addSkillToEvaluationAction(category, skillId);
} catch (error) { } catch (error) {
console.error("Failed to add skill to evaluation:", error); console.error("Failed to add skill:", error);
// En cas d'erreur, rollback l'état local
setCurrentEvaluation(previousEvaluation); setCurrentEvaluation(previousEvaluation);
} }
}; };
@@ -219,8 +264,9 @@ export function EvaluationClientWrapper({
const previousEvaluation = currentEvaluation; const previousEvaluation = currentEvaluation;
try { try {
const updatedEvaluations = currentEvaluation.evaluations.map( // 1. Mise à jour optimiste de l'UI (immédiate)
(catEval) => { const updatedEvaluations: CategoryEvaluation[] =
currentEvaluation.evaluations.map((catEval) => {
if (catEval.category === category) { if (catEval.category === category) {
// Filtrer seulement les compétences qui ne sont pas déjà sélectionnées // Filtrer seulement les compétences qui ne sont pas déjà sélectionnées
const newSkillIds = skillIds.filter( const newSkillIds = skillIds.filter(
@@ -235,7 +281,7 @@ export function EvaluationClientWrapper({
...catEval.skills, ...catEval.skills,
...newSkillIds.map((skillId) => ({ ...newSkillIds.map((skillId) => ({
skillId, skillId,
level: null, level: "never" as SkillLevel,
canMentor: false, canMentor: false,
wantsToLearn: false, wantsToLearn: false,
})), })),
@@ -244,8 +290,7 @@ export function EvaluationClientWrapper({
} }
} }
return catEval; return catEval;
} });
);
const newEvaluation: UserEvaluation = { const newEvaluation: UserEvaluation = {
...currentEvaluation, ...currentEvaluation,
@@ -253,14 +298,16 @@ export function EvaluationClientWrapper({
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),
}; };
// 2. Mettre à jour l'état local immédiatement
setCurrentEvaluation(newEvaluation); setCurrentEvaluation(newEvaluation);
// Ajouter toutes les compétences en parallèle côté API // 3. Persister côté serveur (en arrière-plan)
await Promise.all( await Promise.all(
skillIds.map((skillId) => addSkillToEvaluationAction(category, skillId)) skillIds.map((skillId) => addSkillToEvaluationAction(category, skillId))
); );
} catch (error) { } catch (error) {
console.error("Failed to add multiple skills to evaluation:", error); console.error("Failed to add multiple skills:", error);
// En cas d'erreur, rollback l'état local
setCurrentEvaluation(previousEvaluation); setCurrentEvaluation(previousEvaluation);
} }
}; };

View File

@@ -33,7 +33,8 @@ export function SkillEvaluation({
} = useEvaluationContext(); } = useEvaluationContext();
// Utiliser l'évaluation du contexte (avec état optimiste) ou celle des props (SSR) // Utiliser l'évaluation du contexte (avec état optimiste) ou celle des props (SSR)
const activeEvaluations = contextEvaluation?.evaluations || evaluations; const activeEvaluations = contextEvaluation?.evaluations || [];
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const categoryParam = searchParams.get("category"); const categoryParam = searchParams.get("category");
@@ -90,7 +91,7 @@ export function SkillEvaluation({
<div className="space-y-8"> <div className="space-y-8">
<SkillSelector <SkillSelector
categories={categories} categories={categories}
evaluations={activeEvaluations} evaluations={contextEvaluation?.evaluations || []}
selectedCategory={selectedCategory} selectedCategory={selectedCategory}
onAddSkill={addSkillToEvaluation} onAddSkill={addSkillToEvaluation}
onAddMultipleSkills={addMultipleSkillsToEvaluation} onAddMultipleSkills={addMultipleSkillsToEvaluation}

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
@@ -42,13 +42,22 @@ export function SkillSelector({
const currentCategory = categories.find( const currentCategory = categories.find(
(cat) => cat.category === selectedCategory (cat) => cat.category === selectedCategory
); );
// Utiliser les évaluations passées en props (qui viennent du contexte et sont mises à jour)
const currentEvaluation = evaluations.find( const currentEvaluation = evaluations.find(
(evaluation) => evaluation.category === selectedCategory (evaluation) => evaluation.category === selectedCategory
); );
if (!currentCategory) return null; if (!currentCategory) return null;
const selectedSkillIds = currentEvaluation?.selectedSkillIds || []; // Si la catégorie n'existe pas dans l'évaluation, créer une évaluation vide pour cette catégorie
const effectiveEvaluation = currentEvaluation || {
category: selectedCategory,
skills: [],
selectedSkillIds: [],
};
const selectedSkillIds = effectiveEvaluation.selectedSkillIds || [];
const filteredSkills = currentCategory.skills.filter( const filteredSkills = currentCategory.skills.filter(
(skill) => (skill) =>

View File

@@ -228,12 +228,14 @@ export class EvaluationService {
// 1. Upsert user // 1. Upsert user
const userId = await this.upsertUser(evaluation.profile); const userId = await this.upsertUser(evaluation.profile);
// 2. Upsert user_evaluation // 2. Upsert user_evaluation - d'abord supprimer l'ancienne si elle existe
await client.query("DELETE FROM user_evaluations WHERE user_id = $1", [
userId,
]);
const userEvalQuery = ` const userEvalQuery = `
INSERT INTO user_evaluations (user_id, last_updated) INSERT INTO user_evaluations (user_id, last_updated)
VALUES ($1, $2) VALUES ($1, $2)
ON CONFLICT (user_id)
DO UPDATE SET last_updated = $2
RETURNING id RETURNING id
`; `;
@@ -244,12 +246,6 @@ export class EvaluationService {
const userEvaluationId = userEvalResult.rows[0].id; const userEvaluationId = userEvalResult.rows[0].id;
// 3. Supprimer les anciennes évaluations de skills
await client.query(
"DELETE FROM skill_evaluations WHERE user_evaluation_id = $1",
[userEvaluationId]
);
// 4. Sauvegarder les nouvelles évaluations directement // 4. Sauvegarder les nouvelles évaluations directement
for (const catEval of evaluation.evaluations) { for (const catEval of evaluation.evaluations) {
await this.saveSkillEvaluations(client, userEvaluationId, catEval); await this.saveSkillEvaluations(client, userEvaluationId, catEval);