Files
iag-dev-evaluator/src/actions/evaluations.ts
Froidefond Julien cfde81b8de
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 7m6s
feat: auto-save ciblé au blur avec feedback violet sur tous les champs
- Nouvelle action updateDimensionScore pour sauvegarder un seul champ
  en base sans envoyer tout le formulaire
- DimensionCard : blur sur notes, justification, exemples, confiance
  → upsert ciblé + bordure violette 800ms
- CandidateForm : même pattern sur tous les champs du cartouche
- Bouton save passe aussi en violet (cohérence visuelle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 08:29:51 +01:00

215 lines
7.3 KiB
TypeScript

"use server";
import { auth } from "@/auth";
import { prisma } from "@/lib/db";
import { canAccessEvaluation } from "@/lib/evaluation-access";
import { getEvaluation } from "@/lib/server-data";
import { revalidatePath } from "next/cache";
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
export async function fetchEvaluation(id: string): Promise<ActionResult<Awaited<ReturnType<typeof getEvaluation>>>> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const evaluation = await getEvaluation(id);
if (!evaluation) return { success: false, error: "Évaluation introuvable" };
return { success: true, data: evaluation };
}
export async function deleteEvaluation(id: string): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
try {
await prisma.evaluation.delete({ where: { id } });
revalidatePath("/dashboard");
return { success: true };
} catch (e) {
console.error(e);
return { success: false, error: "Erreur lors de la suppression" };
}
}
export async function createEvaluation(data: {
candidateName: string;
candidateRole: string;
candidateTeam?: string;
evaluationDate: string;
templateId: string;
}): Promise<ActionResult<{ id: string }>> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const { candidateName, candidateRole, candidateTeam, evaluationDate, templateId } = data;
if (!candidateName || !candidateRole || !evaluationDate || !templateId) {
return { success: false, error: "Champs requis manquants" };
}
try {
const evaluatorName = session.user.name || session.user.email || "Évaluateur";
const template = await prisma.template.findUnique({
where: { id: templateId },
include: { dimensions: { orderBy: { orderIndex: "asc" } } },
});
if (!template) return { success: false, error: "Template introuvable" };
const evaluation = await prisma.evaluation.create({
data: {
candidateName,
candidateRole,
candidateTeam: candidateTeam || null,
evaluatorName,
evaluatorId: session.user.id,
evaluationDate: new Date(evaluationDate),
templateId,
status: "draft",
},
});
for (const dim of template.dimensions) {
await prisma.dimensionScore.create({
data: { evaluationId: evaluation.id, dimensionId: dim.id },
});
}
revalidatePath("/dashboard");
return { success: true, data: { id: evaluation.id } };
} catch (e) {
console.error(e);
return { success: false, error: "Erreur lors de la création" };
}
}
export interface UpdateEvaluationInput {
candidateName?: string;
candidateRole?: string;
candidateTeam?: string | null;
evaluatorName?: string;
evaluationDate?: string;
status?: string;
findings?: string | null;
recommendations?: string | null;
isPublic?: boolean;
dimensionScores?: {
dimensionId: string;
evaluationId: string;
score: number | null;
justification?: string | null;
examplesObserved?: string | null;
confidence?: string | null;
candidateNotes?: string | null;
}[];
}
export async function updateDimensionScore(
evaluationId: string,
dimensionId: string,
data: { score?: number | null; justification?: string | null; examplesObserved?: string | null; confidence?: string | null; candidateNotes?: string | null }
): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(evaluationId, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
try {
await prisma.dimensionScore.upsert({
where: { evaluationId_dimensionId: { evaluationId, dimensionId } },
update: data,
create: { evaluationId, dimensionId, ...data },
});
revalidatePath(`/evaluations/${evaluationId}`);
return { success: true };
} catch (e) {
return { success: false, error: e instanceof Error ? e.message : "Erreur" };
}
}
export async function updateEvaluation(id: string, data: UpdateEvaluationInput): Promise<ActionResult> {
const session = await auth();
if (!session?.user) return { success: false, error: "Non authentifié" };
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
if (!hasAccess) return { success: false, error: "Accès refusé" };
const existing = await prisma.evaluation.findUnique({ where: { id } });
if (!existing) return { success: false, error: "Évaluation introuvable" };
try {
const {
candidateName,
candidateRole,
candidateTeam,
evaluatorName,
evaluationDate,
status,
findings,
recommendations,
isPublic,
dimensionScores,
} = data;
const updateData: Record<string, unknown> = {};
if (candidateName != null) updateData.candidateName = candidateName;
if (candidateRole != null) updateData.candidateRole = candidateRole;
if (candidateTeam !== undefined) updateData.candidateTeam = candidateTeam;
if (evaluatorName != null) updateData.evaluatorName = evaluatorName;
if (evaluationDate != null) {
const d = new Date(evaluationDate);
if (!isNaN(d.getTime())) updateData.evaluationDate = d;
}
if (status != null) updateData.status = status;
if (findings != null) updateData.findings = findings;
if (recommendations != null) updateData.recommendations = recommendations;
if (typeof isPublic === "boolean") updateData.isPublic = isPublic;
if (Object.keys(updateData).length > 0) {
await prisma.auditLog.create({
data: { evaluationId: id, action: "updated", newValue: JSON.stringify(updateData) },
});
await prisma.evaluation.update({ where: { id }, data: updateData as Record<string, unknown> });
}
if (dimensionScores && Array.isArray(dimensionScores)) {
for (const ds of dimensionScores) {
if (ds.dimensionId) {
await prisma.dimensionScore.upsert({
where: {
evaluationId_dimensionId: { evaluationId: id, dimensionId: ds.dimensionId },
},
update: {
score: ds.score,
justification: ds.justification,
examplesObserved: ds.examplesObserved,
confidence: ds.confidence,
candidateNotes: ds.candidateNotes,
},
create: {
evaluationId: id,
dimensionId: ds.dimensionId,
score: ds.score,
justification: ds.justification,
examplesObserved: ds.examplesObserved,
confidence: ds.confidence,
candidateNotes: ds.candidateNotes,
},
});
}
}
}
revalidatePath(`/evaluations/${id}`);
revalidatePath("/dashboard");
return { success: true };
} catch (e) {
console.error(e);
return { success: false, error: e instanceof Error ? e.message : "Erreur lors de la sauvegarde" };
}
}