Files
rikx-beheer/app/projecten/[id]/page.tsx
T
remco-vermeer 9f0bd87c40 Voeg Module 1 Projecten toe met project plan, Rikx calculatie en Rikx rondes
- Projectenpagina met pipeline (standaard) en tabelweergave, filters op
  status, Rikx Ronde en zoeken
- Projectpagina in stappen: Project plan (velden uit de Rikx-aanvraag) en
  Rikx calculatie achter een schuif, live berekend volgens de rekenmethode
- Rikx rondes beheren (label, Rikx waarde, Rikx prijs) en koppelen aan projecten
- Oranje Rikx-menubalk met menu per module, gedeeld door alle pagina's
- Oude offerte-tabellen opgeruimd: leeg wordt verwijderd, met data hernoemd
  naar legacy_*
- CLAUDE.md bijgewerkt naar de huidige stand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:26:30 +02:00

77 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
import { getSession } from "../../../lib/auth";
import { getDb, listRikxRondes, type Project, type ProjectExpertScore } from "../../../lib/db";
import { projectStatusBadgeClass, projectStatusLabel } from "../../../lib/projectStatus";
import { AppHeader } from "../../AppHeader";
import { ProjectDetailForm } from "./ProjectDetailForm";
export default async function ProjectPage({
params,
searchParams,
}: {
params: Promise<{ id: string }>;
searchParams: Promise<{ error?: string; opgeslagen?: string }>;
}) {
const session = await getSession();
if (!session) redirect("/login");
const { id } = await params;
const { error, opgeslagen } = await searchParams;
const db = getDb();
const row = db.prepare("SELECT * FROM projects WHERE id = ?").get(Number(id)) as Project | undefined;
if (!row) notFound();
// node:sqlite levert objecten met null-prototype; spreiden maakt ze doorgeefbaar aan client components.
const project = { ...row };
const expertScores = (
db.prepare("SELECT * FROM project_expert_scores WHERE project_id = ? ORDER BY expert").all(project.id) as ProjectExpertScore[]
).map((s) => ({ ...s }));
const message =
error === "missing"
? { tone: "error", text: "Vul een projectnaam in." }
: error === "invalid"
? { tone: "error", text: "Niet opgeslagen: controleer de getallen (percentage 0–100, expertscores 1–10, geen negatieve waarden)." }
: error === "datum"
? { tone: "error", text: "Niet opgeslagen: controleer de datums (de einddatum mag niet vóór de startdatum liggen)." }
: opgeslagen
? { tone: "ok", text: "Project opgeslagen." }
: null;
return (
<div className="min-h-screen bg-zinc-50 dark:bg-zinc-950">
<AppHeader email={session.email} active="projecten" />
<main className="max-w-5xl mx-auto px-4 sm:px-6 py-8 flex flex-col gap-6">
<Link
href="/projecten"
className="text-sm text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors"
>
← Terug naar projecten
</Link>
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-xl font-semibold text-zinc-900 dark:text-zinc-100">{project.name}</h1>
<span
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${projectStatusBadgeClass(project.status)}`}
>
{projectStatusLabel(project.status)}
</span>
</div>
{message && (
<p
className={
message.tone === "error"
? "text-sm text-red-600 dark:text-red-400"
: "text-sm text-green-700 dark:text-green-400"
}
>
{message.text}
</p>
)}
<ProjectDetailForm project={project} expertScores={expertScores} rondes={listRikxRondes()} />
</main>
</div>
);
}