diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 4ecbfd0..cad3c72 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -24,7 +24,7 @@ export default function LoginPage() { setError("Email ou mot de passe incorrect"); return; } - window.location.href = "/"; + window.location.href = "/dashboard"; } catch { setError("Erreur de connexion"); } finally { diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..a5a6cce --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,167 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import { format } from "date-fns"; +import { ConfirmModal } from "@/components/ConfirmModal"; +import { RadarChart } from "@/components/RadarChart"; + +interface Dimension { + id: string; + title: string; +} + +interface EvalRow { + id: string; + candidateName: string; + candidateRole: string; + candidateTeam?: string | null; + evaluatorName: string; + evaluationDate: string; + template?: { name: string; dimensions?: Dimension[] }; + status: string; + dimensionScores?: { dimensionId: string; score: number | null; dimension?: { title: string } }[]; +} + +function buildRadarData(e: EvalRow) { + const dimensions = e.template?.dimensions ?? []; + const scoreMap = new Map( + (e.dimensionScores ?? []).map((ds) => [ds.dimensionId, ds]) + ); + return dimensions + .filter((dim) => !(dim.title ?? "").startsWith("[Optionnel]")) + .map((dim) => { + const ds = scoreMap.get(dim.id); + const score = ds?.score; + if (score == null) return null; + const s = Number(score); + if (Number.isNaN(s) || s < 0 || s > 5) return null; + const title = dim.title ?? ""; + return { + dimension: title.length > 12 ? title.slice(0, 12) + "…" : title, + score: s, + fullMark: 5, + }; + }) + .filter((d): d is { dimension: string; score: number; fullMark: number } => d != null); +} + +export default function DashboardPage() { + const [evaluations, setEvaluations] = useState([]); + const [loading, setLoading] = useState(true); + const [deleteTarget, setDeleteTarget] = useState(null); + + useEffect(() => { + fetch("/api/evaluations") + .then((r) => r.json()) + .then(setEvaluations) + .catch(() => []) + .finally(() => setLoading(false)); + }, []); + + return ( +
+
+

Évaluations

+ + + nouvelle + +
+ + {loading ? ( +
loading...
+ ) : evaluations.length === 0 ? ( +
+ Aucune évaluation.{" "} + + Créer + +
+ ) : ( +
+ {evaluations.map((e) => { + const radarData = buildRadarData(e); + return ( + +
+
+
+

{e.candidateName}

+

+ {e.candidateRole} + {e.candidateTeam && ` · ${e.candidateTeam}`} +

+
+ + {e.status === "submitted" ? "ok" : "draft"} + +
+
+ {e.evaluatorName} + {format(new Date(e.evaluationDate), "yyyy-MM-dd")} + {e.template?.name ?? ""} +
+
+ {radarData.length > 0 ? ( + + ) : ( +
+ pas de scores +
+ )} +
+
+
+ → ouvrir + +
+ + ); + })} +
+ )} + + { + if (!deleteTarget) return; + const res = await fetch(`/api/evaluations/${deleteTarget.id}`, { method: "DELETE" }); + if (res.ok) setEvaluations((prev) => prev.filter((x) => x.id !== deleteTarget.id)); + else alert("Erreur lors de la suppression"); + }} + onCancel={() => setDeleteTarget(null)} + /> +
+ ); +} diff --git a/src/app/evaluations/[id]/page.tsx b/src/app/evaluations/[id]/page.tsx index d3650e3..2c28c8f 100644 --- a/src/app/evaluations/[id]/page.tsx +++ b/src/app/evaluations/[id]/page.tsx @@ -212,7 +212,7 @@ export default function EvaluationDetailPage() { return (
Évaluation introuvable.{" "} - + ← dashboard
@@ -391,7 +391,7 @@ export default function EvaluationDetailPage() { > soumettre - - - - ); - })} - - )} + - { - if (!deleteTarget) return; - const res = await fetch(`/api/evaluations/${deleteTarget.id}`, { method: "DELETE" }); - if (res.ok) setEvaluations((prev) => prev.filter((x) => x.id !== deleteTarget.id)); - else alert("Erreur lors de la suppression"); - }} - onCancel={() => setDeleteTarget(null)} - /> +
+

+ Fonctionnalités +

+
    +
  • + Templates +

    + Grilles multi-dimensions (Full 15, Short 8) avec rubriques et signaux par niveau. +

    +
  • +
  • + Guide d'entretien +

    + Questions suggérées, relances IA, notation 1→5 avec justification et exemples. +

    +
  • +
  • + Radar & synthèse +

    + Visualisation radar, findings et recommandations générés automatiquement. +

    +
  • +
  • + Export +

    + Export PDF et CSV pour partage et archivage. +

    +
  • +
+
+ +
+

+ IA Gen Maturity Evaluator · Équipe Cars Front +

+
); } diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 17306b1..39163a8 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,9 +14,9 @@ export function Header() { iag-eval diff --git a/src/middleware.ts b/src/middleware.ts index 7dcc001..cfbe5f5 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -3,6 +3,7 @@ import { NextResponse } from "next/server"; export default auth((req) => { const isLoggedIn = !!req.auth; + const isPublicRoute = req.nextUrl.pathname === "/"; const isAuthRoute = req.nextUrl.pathname.startsWith("/auth/login") || req.nextUrl.pathname.startsWith("/auth/signup"); @@ -10,8 +11,9 @@ export default auth((req) => { const isAdminRoute = req.nextUrl.pathname.startsWith("/admin"); if (isApiAuth) return NextResponse.next(); + if (isPublicRoute) return NextResponse.next(); if (isAuthRoute && isLoggedIn) { - return NextResponse.redirect(new URL("/", req.nextUrl)); + return NextResponse.redirect(new URL("/dashboard", req.nextUrl)); } if (!isLoggedIn && !isAuthRoute) { return NextResponse.redirect(new URL("/auth/login", req.nextUrl));