Update Dockerfile and package.json to use Prisma migrations, add bcryptjs and next-auth dependencies, and enhance README instructions for database setup. Refactor Prisma schema to include password hashing for users and implement evaluation sharing functionality. Improve admin page with user management features and integrate session handling for authentication. Enhance evaluation detail page with sharing options and update API routes for access control based on user roles.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m4s

This commit is contained in:
Julien Froidefond
2026-02-20 12:58:47 +01:00
parent 9a734dc1ed
commit f5cbc578b7
30 changed files with 1284 additions and 75 deletions

View File

@@ -7,6 +7,7 @@ import { CandidateForm } from "@/components/CandidateForm";
import { DimensionCard } from "@/components/DimensionCard";
import { RadarChart } from "@/components/RadarChart";
import { ExportModal } from "@/components/ExportModal";
import { ShareModal } from "@/components/ShareModal";
import { ConfirmModal } from "@/components/ConfirmModal";
import { generateFindings, generateRecommendations, computeAverageScore } from "@/lib/export-utils";
@@ -35,6 +36,7 @@ interface Evaluation {
candidateRole: string;
candidateTeam?: string | null;
evaluatorName: string;
evaluatorId?: string | null;
evaluationDate: string;
templateId: string;
template: { id: string; name: string; dimensions: Dimension[] };
@@ -42,6 +44,7 @@ interface Evaluation {
findings: string | null;
recommendations: string | null;
dimensionScores: DimensionScore[];
sharedWith?: { id: string; user: { id: string; email: string; name: string | null } }[];
}
export default function EvaluationDetailPage() {
@@ -53,17 +56,21 @@ export default function EvaluationDetailPage() {
const [saving, setSaving] = useState(false);
const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]);
const [exportOpen, setExportOpen] = useState(false);
const [shareOpen, setShareOpen] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [collapseAllTrigger, setCollapseAllTrigger] = useState(0);
const [users, setUsers] = useState<{ id: string; email: string; name: string | null }[]>([]);
const fetchEval = useCallback(() => {
setLoading(true);
Promise.all([
fetch(`/api/evaluations/${id}`).then((r) => r.json()),
fetch("/api/templates").then((r) => r.json()),
fetch("/api/users").then((r) => r.json()),
])
.then(([evalData, templatesData]) => {
.then(([evalData, templatesData, usersData]) => {
setTemplates(Array.isArray(templatesData) ? templatesData : []);
setUsers(Array.isArray(usersData) ? usersData : []);
if (evalData?.error) {
setEvaluation(null);
return;
@@ -251,6 +258,12 @@ export default function EvaluationDetailPage() {
>
{saving ? "..." : "save"}
</button>
<button
onClick={() => setShareOpen(true)}
className="rounded border border-cyan-500/50 bg-cyan-500/10 px-3 py-1.5 font-mono text-xs text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/20"
>
partager
</button>
<button
onClick={() => setExportOpen(true)}
className="rounded border border-cyan-500/50 bg-cyan-500/10 px-3 py-1.5 font-mono text-xs text-cyan-600 dark:text-cyan-400 hover:bg-cyan-500/20"
@@ -266,8 +279,11 @@ export default function EvaluationDetailPage() {
</div>
)}
<section className="rounded-lg border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 p-4 shadow-sm dark:shadow-none">
<h2 className="mb-3 font-mono text-xs text-zinc-600 dark:text-zinc-500">Session</h2>
<section className="relative overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-600 bg-gradient-to-br from-zinc-50 to-white dark:from-zinc-800/80 dark:to-zinc-800 p-5 shadow-sm dark:shadow-none">
<div className="absolute left-0 top-0 h-full w-1 bg-gradient-to-b from-cyan-500/60 to-cyan-400/40" aria-hidden />
<h2 className="mb-4 font-mono text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
Session
</h2>
<CandidateForm
candidateName={evaluation.candidateName}
candidateRole={evaluation.candidateRole}
@@ -393,6 +409,16 @@ export default function EvaluationDetailPage() {
evaluationId={id}
/>
<ShareModal
isOpen={shareOpen}
onClose={() => setShareOpen(false)}
evaluationId={id}
evaluatorId={evaluation.evaluatorId}
users={users}
sharedWith={evaluation.sharedWith ?? []}
onUpdate={fetchEval}
/>
<ConfirmModal
isOpen={deleteConfirmOpen}
title="Supprimer l'évaluation"