import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { ArrowLeft, Check, Send, X } from "lucide-react"; import { requireSession } from "@/server/auth"; import { dbForTenant } from "@/server/db"; import { hasPermission, requirePermission } from "@/server/rbac"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Pill } from "@/components/mockup-ui"; import { buildContext, renderPolicyHtml, splitPolicyDoc } from "@/lib/policy-render"; import { POLICY_STATUS_LABEL, POLICY_STATUS_TONE, POLICY_TYPE_LABEL } from "@/components/policy-modals"; import { PolicyExpertEditor } from "@/components/policy-expert-editor"; import { approvePolicy, rejectPolicy, submitForApproval, updatePolicyTemplate, updateScopedVariables } from "@/server/actions/policies"; const TEMPLATE_TYPES = new Set(["LEITLINIE", "RICHTLINIE", "VERFAHREN"]); /** Im Dokument vorkommende Variablen (inkl. über BL-Referenzen gebunden), ohne Feature-Flags. §8 */ function docVariableKeys(raw: string, baseline: { blId: string; vorgabe: string }[]): Set { const keys = new Set(); for (const m of raw.matchAll(/\{\{\s*([A-Z][A-Z0-9_]*)\s*\}\}/g)) keys.add(m[1]); const bls = new Set([...raw.matchAll(/BL-[A-Z]+-\d+/g)].map((m) => m[0])); for (const bl of bls) { const p = baseline.find((b) => b.blId === bl); if (p) for (const m of p.vorgabe.matchAll(/\{\{\s*([A-Z][A-Z0-9_]*)\s*\}\}/g)) keys.add(m[1]); } return keys; } export default async function PolicyEditPage({ params, searchParams, }: { params: Promise<{ code: string }>; searchParams: Promise<{ expert?: string }>; }) { const session = await requireSession(); requirePermission(session, "policy:read"); const t = await getTranslations("policies"); const tc = await getTranslations("common"); const db = dbForTenant(session.user.tenantId); const { code } = await params; const { expert: expertParam } = await searchParams; const expert = expertParam === "1"; const decoded = decodeURIComponent(code); if (!hasPermission(session, "policy:write")) redirect(`/policies/${code}`); const doc = await db.policyDocument.findFirst({ where: { code: decoded } }); if (!doc) notFound(); if (!TEMPLATE_TYPES.has(doc.type)) redirect(`/policies/${code}`); const [variables, baseline, users, allDocs] = await Promise.all([ db.policyVariable.findMany({ orderBy: { orderIdx: "asc" } }), db.policyBaselineParam.findMany(), db.user.findMany({ select: { id: true, name: true } }), db.policyDocument.findMany({ select: { code: true, title: true }, orderBy: { orderIdx: "asc" } }), ]); const userName = (id: string | null) => users.find((u) => u.id === id)?.name ?? id ?? "—"; const usedKeys = docVariableKeys(doc.rawMarkdown, baseline); const docVars = variables.filter((v) => usedKeys.has(v.key) && v.kind !== "boolean"); const keysCsv = docVars.map((v) => v.key).join(","); const ctx = buildContext(variables); const { infoMd, bodyMd } = splitPolicyDoc(doc.rawMarkdown); const stripBaseline = doc.code !== "BASELINE"; const bodyHtml = renderPolicyHtml(bodyMd, ctx, { readMode: true, stripBaseline }); const infoHtml = renderPolicyHtml(infoMd, ctx, { readMode: true, stripBaseline: false }); const canApprove = hasPermission(session, "policy:approve"); const isSubmitter = doc.submittedBy === session.user.id; return (
{t("backToDoc")}

{t("editTitle")}

{POLICY_STATUS_LABEL[doc.status]} {doc.code} · {doc.title} · {POLICY_TYPE_LABEL[doc.type]}
{/* Modus-Umschalter: Standard (Variablen) ↔ Experten (Rohtext) */}
{t("modeStandard")} {t("modeExpert")}
{expert ? (
{/* Rohtext-Editor mit Werkzeugleiste */}

{t("template")}

({ key: v.key, title: v.title }))} docs={allDocs} />

{t("expertHint")}

{/* Vorschau (aktualisiert beim Speichern) */}

{t("preview")}

{infoMd && (
{t("docInfo")}
)}
) : (
{/* Gerendertes Dokument */}
{infoMd && (
{t("docInfo")}
)}
{/* Rechte Leiste: Freigabe + Variablen */}
{/* Freigabe-Workflow (Vier-Augen, §8) */}

{t("approval")}

{doc.status === "ENTWURF" && (
)} {doc.status === "IN_FREIGABE" && ( <>

{t("submittedBy")}: {userName(doc.submittedBy)}

{canApprove && !isSubmitter ? ( <>
) : (

{isSubmitter ? t("fourEyesSelf") : t("fourEyesNoRight")}

)} )} {doc.status === "FREIGEGEBEN" && ( <>

{t("approvedBy")}: {userName(doc.approvedBy)}

)}

{t("approvalNote")}

{/* Dokument-Variablen (§8 Variablen-Scoping) */}

{t("docVariables")}

{t("docVariablesHint")}

{docVars.length === 0 &&

{tc("none")}

} {docVars.map((v) => (
))}
{docVars.length > 0 && }
)}
); }