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 (
<EvaluationClientWrapper userEvaluation={userEvaluation} teams={teams}>
<EvaluationClientWrapper
userEvaluation={userEvaluation}
teams={teams}
skillCategories={skillCategories}
>
<div>
{/* Skill Evaluation */}
<SkillEvaluation

View File

@@ -2,7 +2,13 @@
import { useEffect, useState } from "react";
import { useUser } from "@/hooks/use-user-context";
import { UserEvaluation, Team, SkillLevel } from "@/lib/types";
import {
UserEvaluation,
Team,
SkillLevel,
UserProfile,
CategoryEvaluation,
} from "@/lib/types";
import {
updateSkillLevel as updateSkillLevelAction,
updateSkillMentorStatus as updateSkillMentorStatusAction,
@@ -10,19 +16,22 @@ import {
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 { setUserInfo } = useUser();
const { userInfo, setUserInfo } = useUser();
// État local pour l'UI optimiste - commence avec les données SSR
const [currentEvaluation, setCurrentEvaluation] =
@@ -30,8 +39,18 @@ export function EvaluationClientWrapper({
// Met à jour l'état local quand les props changent (SSR)
useEffect(() => {
if (userEvaluation) {
setCurrentEvaluation(userEvaluation);
}, [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 (
@@ -173,9 +192,11 @@ export function EvaluationClientWrapper({
const previousEvaluation = currentEvaluation;
try {
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
// 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,
@@ -184,7 +205,7 @@ export function EvaluationClientWrapper({
...catEval.skills,
{
skillId,
level: null,
level: "never" as SkillLevel,
canMentor: false,
wantsToLearn: false,
},
@@ -193,8 +214,28 @@ export function EvaluationClientWrapper({
}
}
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,
@@ -202,10 +243,14 @@ export function EvaluationClientWrapper({
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 to evaluation:", error);
console.error("Failed to add skill:", error);
// En cas d'erreur, rollback l'état local
setCurrentEvaluation(previousEvaluation);
}
};
@@ -219,8 +264,9 @@ export function EvaluationClientWrapper({
const previousEvaluation = currentEvaluation;
try {
const updatedEvaluations = currentEvaluation.evaluations.map(
(catEval) => {
// 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(
@@ -235,7 +281,7 @@ export function EvaluationClientWrapper({
...catEval.skills,
...newSkillIds.map((skillId) => ({
skillId,
level: null,
level: "never" as SkillLevel,
canMentor: false,
wantsToLearn: false,
})),
@@ -244,8 +290,7 @@ export function EvaluationClientWrapper({
}
}
return catEval;
}
);
});
const newEvaluation: UserEvaluation = {
...currentEvaluation,
@@ -253,14 +298,16 @@ export function EvaluationClientWrapper({
lastUpdated: new Date().toISOString(),
};
// 2. Mettre à jour l'état local immédiatement
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(
skillIds.map((skillId) => addSkillToEvaluationAction(category, skillId))
);
} 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);
}
};

View File

@@ -33,7 +33,8 @@ export function SkillEvaluation({
} = useEvaluationContext();
// 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 router = useRouter();
const categoryParam = searchParams.get("category");
@@ -90,7 +91,7 @@ export function SkillEvaluation({
<div className="space-y-8">
<SkillSelector
categories={categories}
evaluations={activeEvaluations}
evaluations={contextEvaluation?.evaluations || []}
selectedCategory={selectedCategory}
onAddSkill={addSkillToEvaluation}
onAddMultipleSkills={addMultipleSkillsToEvaluation}

View File

@@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -42,13 +42,22 @@ export function SkillSelector({
const currentCategory = categories.find(
(cat) => cat.category === selectedCategory
);
// Utiliser les évaluations passées en props (qui viennent du contexte et sont mises à jour)
const currentEvaluation = evaluations.find(
(evaluation) => evaluation.category === selectedCategory
);
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(
(skill) =>

View File

@@ -228,12 +228,14 @@ export class EvaluationService {
// 1. Upsert user
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 = `
INSERT INTO user_evaluations (user_id, last_updated)
VALUES ($1, $2)
ON CONFLICT (user_id)
DO UPDATE SET last_updated = $2
RETURNING id
`;
@@ -244,12 +246,6 @@ export class EvaluationService {
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
for (const catEval of evaluation.evaluations) {
await this.saveSkillEvaluations(client, userEvaluationId, catEval);