Files
remco-vermeer 53bf667f54 Voeg Module 4 experts, waardebepalingspagina en projectverbeteringen toe
Experts (Module 4)
- Pagina Experts (CRM): experts uitnodigen per e-mail (zonder SMTP wordt de
  link getoond), rondes koppelen, opnieuw uitnodigen, verwijderen en
  "Inloggen als" (alleen lezen) om mee te kijken
- Uitnodigingspagina waar een expert zijn wachtwoord instelt (token 7 dagen
  geldig, eenmalig, alleen als hash opgeslagen)
- Expertpagina: projecten in waardebepaling uit de eigen rondes, project plan
  lezen en eigen beoordeling geven (3 scores, advies Bonus/Malus, toelichting)
- Rollen admin/expert: alle beheerpagina's en -acties controleren de rol
- Rikx calculatie gebruikt de beoordelingen van de experts in plaats van de
  handmatige expertkolommen; experts koppelen ook vanaf de rondepagina

Waardebepaling projecten
- Nieuwe pagina met projecten in waardebepaling op kosten/impact (laag naar
  hoog), filter op ronde, Rikx Waarde-veld dat live doorrekent en opslaat in
  de ronde, en een 75%-lijn bij een gekozen ronde

Projecten
- Rikx calculatie als tab, zichtbaar vanaf status Waardebepaling, met keuze
  rekenmethode (Arbeid; Jeugd nog niet beschikbaar) en beoordeling
  goedgekeurd/afgekeurd met reden
- Impactmaker gekoppeld aan een sociaal ondernemer
- Opslaan-knop bovenaan; ongeldige velden op een verborgen tab worden getoond
- Project toevoegen maakt direct een project aan; aparte pagina verwijderd
- Waarderingssystematiek Rikx juni 2023 toegevoegd aan _doc

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

91 lines
3.4 KiB
TypeScript
Raw Permalink 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 { requireAdmin } from "../../../lib/auth";
import { getDb, listRikxRondes, type Project } from "../../../lib/db";
import { listBeoordelingen } from "../../../lib/experts";
import { projectStatusBadgeClass, projectStatusLabel } from "../../../lib/projectStatus";
import { listAccountNames } from "../../../lib/platform-db";
import { AppHeader } from "../../AppHeader";
import { PROJECT_FORM_ID } from "./formId";
import { ProjectDetailForm } from "./ProjectDetailForm";
export default async function ProjectPage({
params,
searchParams,
}: {
params: Promise<{ id: string }>;
searchParams: Promise<{ error?: string; opgeslagen?: string; tab?: string }>;
}) {
const session = await requireAdmin();
const { id } = await params;
const { error, opgeslagen, tab } = 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 beoordelingen = listBeoordelingen([project.id]);
const message =
error === "missing"
? { tone: "error", text: "Vul een projectnaam in." }
: error === "invalid"
? { tone: "error", text: "Niet opgeslagen: controleer de getallen (percentage 0–100, 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>
{/* Verstuurt het projectformulier hieronder (form-attribuut) */}
<button
type="submit"
form={PROJECT_FORM_ID}
className="ml-auto rounded-lg bg-rikx-maroon hover:bg-rikx-maroon-light px-5 py-2 text-sm font-medium text-white transition-colors"
>
Opslaan
</button>
</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}
beoordelingen={beoordelingen}
rondes={listRikxRondes()}
impactmakers={listAccountNames()}
initialTab={tab === "calculatie" ? "calculatie" : "plan"}
/>
</main>
</div>
);
}