import Link from "next/link"; import { getFormatter } from "next-intl/server"; import { Pencil, Plus, Trash2 } from "lucide-react"; import type { ClassificationClass, CryptoEntry, HandbookTopic, HandlingAspect, HandlingRule, PolicyDocument, PolicyVariable, RiskDamageDimension, RiskEwLevel, RiskMatrixClass, } from "@prisma/client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Pill } from "@/components/mockup-ui"; import { isExpired, isExpiring } from "@/lib/supplier"; import { applyProtection, buildContext, renderPolicyHtml, resolveLink } from "@/lib/policy-render"; import { addClassificationClass, addCryptoEntry, addHandlingAspect, deleteCryptoEntry, updateCryptoEntry, updateDamageDimension, updateEwLevel, updateHandlingRule, updateRiskClass, } from "@/server/actions/policies"; const TONE_HEX: Record = { ok: "#2ea86b", warn: "#e3b427", orange: "#e2802e", risk: "#d63c5e" }; export interface RegisterData { crypto?: CryptoEntry[]; classes?: ClassificationClass[]; aspects?: HandlingAspect[]; rules?: HandlingRule[]; riskClasses?: RiskMatrixClass[]; ewLevels?: RiskEwLevel[]; damage?: RiskDamageDimension[]; handbook?: HandbookTopic[]; variables?: PolicyVariable[]; } /** Inhalt der verwalteten Register (§7b) und des Anwender-Handbuchs (§9.7) — auf eigener Seite. */ export async function PolicyRegisterView({ doc, data, canWrite, }: { doc: PolicyDocument; data: RegisterData; canWrite: boolean; }) { return (
{doc.code === "CRYPTO" && } {doc.code === "CLASSIFICATION" && ( )} {doc.code === "RISKMATRIX" && ( )} {doc.code === "HANDBUCH" && }
); } /* ─────────────── Krypto-Register ─────────────── */ async function CryptoRegister({ entries, canWrite }: { entries: CryptoEntry[]; canWrite: boolean }) { const fmt = await getFormatter(); const date = (d: Date | null) => (d ? fmt.dateTime(d, { dateStyle: "medium" }) : "—"); const expiring = entries.filter((e) => isExpiring(e.ablaufdatum) || isExpired(e.ablaufdatum)).length; return (

Eingesetzte Verschlüsselung mit Ablaufüberwachung.{" "} {expiring > 0 && {expiring} laufen bald ab / abgelaufen.}

{canWrite && (
Eintrag
)}
{canWrite && {entries.length === 0 && ( )} {entries.map((e) => { const exp = isExpired(e.ablaufdatum); const soon = !exp && isExpiring(e.ablaufdatum); return ( {canWrite && ( )} ); })}
Dienst Schlüssel Algorithmus Ablauf Verantwortlich Baseline}
Noch keine Einträge.
{e.dienst} {e.schluessel} {e.algorithmus ?? "—"} {date(e.ablaufdatum)} {exp && abgelaufen} {soon && läuft ab} {e.verantwortlich} {e.baselineRef ? {e.baselineRef} : }
); } /* ─────────────── Klassifizierungs-Handhabungsmatrix ─────────────── */ function ClassificationMatrix({ classes, aspects, rules, canWrite, }: { classes: ClassificationClass[]; aspects: HandlingAspect[]; rules: HandlingRule[]; canWrite: boolean }) { const ruleOf = (classId: string, aspectId: string) => rules.find((r) => r.classId === classId && r.aspectId === aspectId)?.text ?? ""; return (

Handhabungsregeln je Schutzklasse. Assets erben die Regeln aus ihrer Klasse.

{canWrite && (
Aspekt
Schutzklasse
)}
{classes.map((c) => ( ))} {aspects.map((a) => ( {classes.map((c) => ( ))} ))}
Aspekt{c.name}
{a.name} {canWrite ? (
{ruleOf(c.id, a.id) || }
) : ( {ruleOf(c.id, a.id) || "—"} )}
); } /* ─────────────── Risiko-Bewertungsmatrix ─────────────── */ function RiskMatrix({ classes, ew, damage, canWrite, }: { classes: RiskMatrixClass[]; ew: RiskEwLevel[]; damage: RiskDamageDimension[]; canWrite: boolean }) { const sorted = [...classes].sort((a, b) => a.maxScore - b.maxScore); const classFor = (score: number) => sorted.find((c) => score <= c.maxScore) ?? sorted[sorted.length - 1]; const rows = [4, 3, 2, 1]; // Schadensausmaß (oben = 4) const cols = [1, 2, 3, 4]; // Eintrittswahrscheinlichkeit return (
{/* 4×4 Heatmap */}

Bewertungsmatrix (Schadensausmaß × Eintrittswahrscheinlichkeit)

{rows.map((s) => ( {cols.map((e) => { const score = s * e; const cls = classFor(score); return ( ); })} ))} ))}
S {s} {score}
{cols.map((e) => ( EW {e}
{/* Risikoklassen + Akzeptanzinstanz — inline editierbar */}

Risikoklassen & Akzeptanz {canWrite && · zum Bearbeiten auf eine Zeile klicken}

KlasseBis RisikowertAkzeptanzinstanz
{sorted.map((c) => ( canWrite ? (
{c.name} ≤ {c.maxScore} {c.acceptance}
) : (
{c.name} ≤ {c.maxScore} {c.acceptance}
) ))}
{/* Eintrittswahrscheinlichkeit — inline editierbar */}

Eintrittswahrscheinlichkeit

{[...ew].sort((a, b) => a.level - b.level).map((l) => ( canWrite ? (
{l.level} · {l.label} — {l.definition}
) : (
{l.level} · {l.label} — {l.definition}
) ))}
{/* Schadenskategorien — inline editierbar */}

Schadenskategorien

Dimension1 Niedrig2 Normal3 Hoch4 Sehr hoch
{damage.map((d) => { const lv = d.levels as Record; return canWrite ? (
{d.name} {lv["1"]}{lv["2"]}{lv["3"]}{lv["4"]}
) : (
{d.name} {lv["1"]}{lv["2"]}{lv["3"]}{lv["4"]}
); })}
); } /* ─────────────── Anwender-Handbuch ─────────────── */ function Handbook({ topics, variables }: { topics: HandbookTopic[]; variables: PolicyVariable[] }) { const ctx = applyProtection(buildContext(variables)); const cats = [...new Set(topics.map((t) => t.category))]; return (

Kompakte Regeln für Mitarbeitende. Werte (z. B. Passwortlänge) stammen aus der zentralen Baseline und bleiben automatisch synchron.

{cats.map((cat) => (

{cat}

{topics.filter((t) => t.category === cat).map((topic) => (

{topic.title}

{topic.sourceRefs.length > 0 && (
Details: {topic.sourceRefs.map((ref) => { const { href, label } = resolveLink(ref); return ( {label} → ); })}
)}
))}
))}
); }