Richtlinien-Editor: Experten-Modus (Rohtext) mit Einfüge-Hilfen & Auto-Variablen
Ergänzung zum variablenbasierten Standard-Bearbeitungsmodus:
- Modus-Umschalter „Standard (Variablen) ↔ Experten-Modus" auf /policies/[code]/edit.
- Experten-Modus (Client-Editor policy-expert-editor.tsx): voller Vorlagen-/Markdown-
Editor mit Formatierungsleiste (fett/kursiv/Überschrift/Liste/Tabelle),
Einfüge-Dropdowns für Variablen und Dokument-/Register-Verweise ({{LINK:CODE}},
Deep-Links {{LINK:R08#4.1.2}}), „Neue Variable"-Feld; Live-Vorschau (bei Speichern).
- Beim Speichern werden neue {{VARIABLEN}} automatisch als PolicyVariable angelegt
(danach im Standard-Modus pflegbar).
Verifiziert: Umschalter, Editor + Toolbar, „Neue Variable" fügt {{STANDORT_WERK}}
ein, Speichern legt die Variable automatisch an; Vorschau rendert.
Zu Bildern/KI-Formulierungshilfe: siehe Antwort — Bilder brauchen Storage/Upload,
KI-Hilfe braucht Anthropic-API-Anbindung (beides als eigener Schritt sinnvoll).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ 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 { approvePolicy, rejectPolicy, submitForApproval, updateScopedVariables } from "@/server/actions/policies";
|
||||
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"]);
|
||||
|
||||
@@ -26,13 +27,21 @@ function docVariableKeys(raw: string, baseline: { blId: string; vorgabe: string
|
||||
return keys;
|
||||
}
|
||||
|
||||
export default async function PolicyEditPage({ params }: { params: Promise<{ code: string }> }) {
|
||||
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}`);
|
||||
@@ -41,10 +50,11 @@ export default async function PolicyEditPage({ params }: { params: Promise<{ cod
|
||||
if (!doc) notFound();
|
||||
if (!TEMPLATE_TYPES.has(doc.type)) redirect(`/policies/${code}`);
|
||||
|
||||
const [variables, baseline, users] = await Promise.all([
|
||||
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 ?? "—";
|
||||
|
||||
@@ -73,6 +83,44 @@ export default async function PolicyEditPage({ params }: { params: Promise<{ cod
|
||||
<span className="text-[12.5px] text-muted-foreground">{doc.code} · {doc.title} · {POLICY_TYPE_LABEL[doc.type]}</span>
|
||||
</div>
|
||||
|
||||
{/* Modus-Umschalter: Standard (Variablen) ↔ Experten (Rohtext) */}
|
||||
<div className="mb-5 inline-flex rounded-lg border bg-card p-1 text-[12.5px] font-semibold">
|
||||
<Link href={`/policies/${code}/edit`} className={!expert ? "rounded-md bg-[var(--sidebar-accent)] px-3 py-1.5 text-[var(--primary)]" : "px-3 py-1.5 text-muted-foreground hover:text-foreground"}>{t("modeStandard")}</Link>
|
||||
<Link href={`/policies/${code}/edit?expert=1`} className={expert ? "rounded-md bg-[var(--sidebar-accent)] px-3 py-1.5 text-[var(--primary)]" : "px-3 py-1.5 text-muted-foreground hover:text-foreground"}>{t("modeExpert")}</Link>
|
||||
</div>
|
||||
|
||||
{expert ? (
|
||||
<div className="grid gap-5 lg:grid-cols-[1.4fr_1fr]">
|
||||
{/* Rohtext-Editor mit Werkzeugleiste */}
|
||||
<div>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<p className="font-heading text-sm font-semibold">{t("template")}</p>
|
||||
<Button type="submit" form="policy-template" size="sm">{tc("save")}</Button>
|
||||
</div>
|
||||
<PolicyExpertEditor
|
||||
formId="policy-template"
|
||||
saveAction={updatePolicyTemplate.bind(null, decoded)}
|
||||
initialMarkdown={doc.rawMarkdown}
|
||||
variables={variables.map((v) => ({ key: v.key, title: v.title }))}
|
||||
docs={allDocs}
|
||||
/>
|
||||
<p className="mt-2 text-[11.5px] text-muted-foreground">{t("expertHint")}</p>
|
||||
</div>
|
||||
{/* Vorschau (aktualisiert beim Speichern) */}
|
||||
<div>
|
||||
<p className="mb-3 font-heading text-sm font-semibold">{t("preview")}</p>
|
||||
<div className="shadow-card max-h-[80vh] overflow-y-auto rounded-2xl border bg-card p-6">
|
||||
{infoMd && (
|
||||
<details className="mb-4 rounded-xl border bg-[var(--surface-soft)]">
|
||||
<summary className="cursor-pointer list-none px-4 py-2.5 text-[12.5px] font-semibold text-muted-foreground select-none hover:text-foreground [&::-webkit-details-marker]:hidden">{t("docInfo")}</summary>
|
||||
<div className="policy-prose border-t px-4 py-2" dangerouslySetInnerHTML={{ __html: infoHtml }} />
|
||||
</details>
|
||||
)}
|
||||
<article className="policy-prose" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-5 lg:grid-cols-[1fr_300px]">
|
||||
{/* Gerendertes Dokument */}
|
||||
<div className="shadow-card rounded-2xl border bg-card p-6">
|
||||
@@ -150,6 +198,7 @@ export default async function PolicyEditPage({ params }: { params: Promise<{ cod
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user