Referenzmatrix nach Mockup + Risiko-Bewertungsmatrix editierbar
Referenz-/Coverage-Matrix (§9.3) wie im GEFIM-Mockup aufgebaut: - Zwei Richtungen „nach Control" und „nach Dokument" (Umschalter) + Assessment- Export-Button (Platzhalter). - nach Control: Control-Nr + -Titel, je Anforderung MUSS/SOLL-Badge + Text, Richtlinie-Link, Verfahren-Chips. - nach Dokument: gruppiert je Richtlinie (Kopfzeile), Zeilen Control · Anforderung · Umsetzung (Variablen aufgelöst, BL entfernt) · Verfahren. - 44 Control-Titel aus dem Mockup übernommen (lib/control-titles.ts), numerische Control-Sortierung. Risiko-Bewertungsmatrix jetzt editierbar (war nur anzeigend / Edit-Popover wurde vom overflow-Container abgeschnitten): - Risikoklassen, Eintrittswahrscheinlichkeit UND Schadenskategorien inline bearbeitbar (Klick auf Zeile klappt Formular auf — kein abgeschnittenes Popover). - Neue Actions updateEwLevel/updateDamageDimension; Krypto-Register-Clipping (overflow-x-auto) ebenfalls entfernt. Verifiziert: beide Referenz-Richtungen gerendert; Risikoklasse-Edit end-to-end gespeichert (persistiert). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,8 @@ import {
|
||||
addHandlingAspect,
|
||||
deleteCryptoEntry,
|
||||
updateCryptoEntry,
|
||||
updateDamageDimension,
|
||||
updateEwLevel,
|
||||
updateHandlingRule,
|
||||
updateRiskClass,
|
||||
} from "@/server/actions/policies";
|
||||
@@ -98,7 +100,7 @@ async function CryptoRegister({ entries, canWrite }: { entries: CryptoEntry[]; c
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
<div className="overflow-x-auto rounded-xl border">
|
||||
<div className="rounded-xl border">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b bg-[var(--elevated)] text-[10.5px] tracking-[.04em] text-muted-foreground uppercase">
|
||||
@@ -276,73 +278,94 @@ function RiskMatrix({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Risikoklassen + Akzeptanzinstanz */}
|
||||
{/* Risikoklassen + Akzeptanzinstanz — inline editierbar */}
|
||||
<section>
|
||||
<p className="mb-2 font-heading text-sm font-semibold">Risikoklassen & Akzeptanz</p>
|
||||
<div className="overflow-hidden rounded-xl border">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr className="border-b bg-[var(--elevated)] text-[10.5px] tracking-[.04em] text-muted-foreground uppercase">
|
||||
<th className="p-2.5 text-left">Klasse</th><th className="p-2.5 text-left">Bis Risikowert</th><th className="p-2.5 text-left">Akzeptanzinstanz</th>{canWrite && <th className="p-2.5" />}
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{sorted.map((c) => (
|
||||
<tr key={c.id} className="border-b last:border-0">
|
||||
<td className="p-2.5"><Pill tone={(c.tone as "ok" | "warn" | "orange" | "risk")}>{c.name}</Pill></td>
|
||||
<td className="p-2.5 font-medium">≤ {c.maxScore}</td>
|
||||
<td className="p-2.5 text-muted-foreground">{c.acceptance}</td>
|
||||
{canWrite && (
|
||||
<td className="p-2.5 text-right">
|
||||
<details className="relative">
|
||||
<summary className="grid size-7 cursor-pointer list-none place-items-center rounded-md text-muted-foreground hover:bg-muted [&::-webkit-details-marker]:hidden"><Pencil className="size-3.5" /></summary>
|
||||
<form action={updateRiskClass.bind(null, c.id)} className="shadow-card absolute right-0 z-10 mt-2 w-72 space-y-2 rounded-xl border bg-card p-3 text-sm">
|
||||
<Input name="name" required defaultValue={c.name} />
|
||||
<Input name="maxScore" type="number" required defaultValue={c.maxScore} placeholder="Bis Risikowert" />
|
||||
<Input name="acceptance" defaultValue={c.acceptance} placeholder="Akzeptanzinstanz" />
|
||||
<Button type="submit" variant="secondary" size="sm">Speichern</Button>
|
||||
</form>
|
||||
</details>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="mb-2 font-heading text-sm font-semibold">Risikoklassen & Akzeptanz {canWrite && <span className="text-[11px] font-normal text-muted-foreground">· zum Bearbeiten auf eine Zeile klicken</span>}</p>
|
||||
<div className="rounded-xl border">
|
||||
<div className="grid grid-cols-[8rem_7rem_1fr] gap-x-3 border-b bg-[var(--elevated)] px-3 py-2 text-[10.5px] tracking-[.04em] text-muted-foreground uppercase">
|
||||
<span>Klasse</span><span>Bis Risikowert</span><span>Akzeptanzinstanz</span>
|
||||
</div>
|
||||
{sorted.map((c) => (
|
||||
canWrite ? (
|
||||
<details key={c.id} className="border-b last:border-0">
|
||||
<summary className="grid cursor-pointer grid-cols-[8rem_7rem_1fr] items-center gap-x-3 px-3 py-2.5 text-sm select-none hover:bg-muted/40 [&::-webkit-details-marker]:hidden">
|
||||
<span><Pill tone={c.tone as "ok" | "warn" | "orange" | "risk"}>{c.name}</Pill></span>
|
||||
<span className="font-medium">≤ {c.maxScore}</span>
|
||||
<span className="flex items-center justify-between gap-2 text-muted-foreground">{c.acceptance}<Pencil className="size-3.5 shrink-0 opacity-60" /></span>
|
||||
</summary>
|
||||
<form action={updateRiskClass.bind(null, c.id)} className="grid gap-2 border-t bg-[var(--surface-soft)] p-3 sm:grid-cols-[8rem_7rem_1fr_auto]">
|
||||
<Input name="name" required defaultValue={c.name} placeholder="Klasse" className="h-8" />
|
||||
<Input name="maxScore" type="number" required defaultValue={c.maxScore} placeholder="≤ Wert" className="h-8" />
|
||||
<Input name="acceptance" defaultValue={c.acceptance} placeholder="Akzeptanzinstanz" className="h-8" />
|
||||
<Button type="submit" variant="secondary" size="sm">Speichern</Button>
|
||||
</form>
|
||||
</details>
|
||||
) : (
|
||||
<div key={c.id} className="grid grid-cols-[8rem_7rem_1fr] items-center gap-x-3 border-b px-3 py-2.5 text-sm last:border-0">
|
||||
<span><Pill tone={c.tone as "ok" | "warn" | "orange" | "risk"}>{c.name}</Pill></span>
|
||||
<span className="font-medium">≤ {c.maxScore}</span>
|
||||
<span className="text-muted-foreground">{c.acceptance}</span>
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Eintrittswahrscheinlichkeit */}
|
||||
{/* Eintrittswahrscheinlichkeit — inline editierbar */}
|
||||
<section>
|
||||
<p className="mb-2 font-heading text-sm font-semibold">Eintrittswahrscheinlichkeit</p>
|
||||
<ul className="space-y-1 text-[12.5px]">
|
||||
{ew.sort((a, b) => a.level - b.level).map((l) => (
|
||||
<li key={l.id}><b>{l.level} · {l.label}</b> <span className="text-muted-foreground">— {l.definition}</span></li>
|
||||
<div className="rounded-xl border">
|
||||
{[...ew].sort((a, b) => a.level - b.level).map((l) => (
|
||||
canWrite ? (
|
||||
<details key={l.id} className="border-b last:border-0">
|
||||
<summary className="flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-[12.5px] select-none hover:bg-muted/40 [&::-webkit-details-marker]:hidden">
|
||||
<span><b>{l.level} · {l.label}</b> <span className="text-muted-foreground">— {l.definition}</span></span>
|
||||
<Pencil className="size-3.5 shrink-0 opacity-60" />
|
||||
</summary>
|
||||
<form action={updateEwLevel.bind(null, l.id)} className="grid gap-2 border-t bg-[var(--surface-soft)] p-3 sm:grid-cols-[10rem_1fr_auto]">
|
||||
<Input name="label" required defaultValue={l.label} placeholder="Bezeichnung" className="h-8" />
|
||||
<Input name="definition" defaultValue={l.definition} placeholder="Definition" className="h-8" />
|
||||
<Button type="submit" variant="secondary" size="sm">Speichern</Button>
|
||||
</form>
|
||||
</details>
|
||||
) : (
|
||||
<div key={l.id} className="border-b px-3 py-2 text-[12.5px] last:border-0"><b>{l.level} · {l.label}</b> <span className="text-muted-foreground">— {l.definition}</span></div>
|
||||
)
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Schadenskategorien */}
|
||||
{/* Schadenskategorien — inline editierbar */}
|
||||
<section>
|
||||
<p className="mb-2 font-heading text-sm font-semibold">Schadenskategorien</p>
|
||||
<div className="overflow-x-auto rounded-xl border">
|
||||
<table className="w-full text-[12px]">
|
||||
<thead><tr className="border-b bg-[var(--elevated)] text-[10.5px] tracking-[.04em] text-muted-foreground uppercase">
|
||||
<th className="p-2.5 text-left">Dimension</th><th className="p-2.5 text-left">1 Niedrig</th><th className="p-2.5 text-left">2 Normal</th><th className="p-2.5 text-left">3 Hoch</th><th className="p-2.5 text-left">4 Sehr hoch</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{damage.map((d) => {
|
||||
const lv = d.levels as Record<string, string>;
|
||||
return (
|
||||
<tr key={d.id} className="border-b last:border-0 align-top">
|
||||
<td className="p-2.5 font-medium">{d.name}</td>
|
||||
<td className="p-2.5 text-muted-foreground">{lv["1"]}</td>
|
||||
<td className="p-2.5 text-muted-foreground">{lv["2"]}</td>
|
||||
<td className="p-2.5 text-muted-foreground">{lv["3"]}</td>
|
||||
<td className="p-2.5 text-muted-foreground">{lv["4"]}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="rounded-xl border">
|
||||
<div className="hidden grid-cols-[12rem_1fr_1fr_1fr_1fr] gap-x-3 border-b bg-[var(--elevated)] px-3 py-2 text-[10.5px] tracking-[.04em] text-muted-foreground uppercase sm:grid">
|
||||
<span>Dimension</span><span>1 Niedrig</span><span>2 Normal</span><span>3 Hoch</span><span>4 Sehr hoch</span>
|
||||
</div>
|
||||
{damage.map((d) => {
|
||||
const lv = d.levels as Record<string, string>;
|
||||
return canWrite ? (
|
||||
<details key={d.id} className="border-b last:border-0">
|
||||
<summary className="grid cursor-pointer grid-cols-1 items-center gap-x-3 px-3 py-2.5 text-[12px] select-none hover:bg-muted/40 sm:grid-cols-[12rem_1fr_1fr_1fr_1fr] [&::-webkit-details-marker]:hidden">
|
||||
<span className="flex items-center justify-between gap-2 font-medium">{d.name}<Pencil className="size-3.5 shrink-0 opacity-60 sm:hidden" /></span>
|
||||
<span className="text-muted-foreground">{lv["1"]}</span><span className="text-muted-foreground">{lv["2"]}</span><span className="text-muted-foreground">{lv["3"]}</span><span className="text-muted-foreground">{lv["4"]}</span>
|
||||
</summary>
|
||||
<form action={updateDamageDimension.bind(null, d.id)} className="grid gap-2 border-t bg-[var(--surface-soft)] p-3 sm:grid-cols-2">
|
||||
<Input name="name" required defaultValue={d.name} placeholder="Dimension" className="h-8 sm:col-span-2" />
|
||||
<Input name="l1" defaultValue={lv["1"]} placeholder="1 Niedrig" className="h-8" />
|
||||
<Input name="l2" defaultValue={lv["2"]} placeholder="2 Normal" className="h-8" />
|
||||
<Input name="l3" defaultValue={lv["3"]} placeholder="3 Hoch" className="h-8" />
|
||||
<Input name="l4" defaultValue={lv["4"]} placeholder="4 Sehr hoch" className="h-8" />
|
||||
<Button type="submit" variant="secondary" size="sm" className="sm:col-span-2">Speichern</Button>
|
||||
</form>
|
||||
</details>
|
||||
) : (
|
||||
<div key={d.id} className="grid grid-cols-1 gap-x-3 border-b px-3 py-2.5 text-[12px] last:border-0 sm:grid-cols-[12rem_1fr_1fr_1fr_1fr]">
|
||||
<span className="font-medium">{d.name}</span>
|
||||
<span className="text-muted-foreground">{lv["1"]}</span><span className="text-muted-foreground">{lv["2"]}</span><span className="text-muted-foreground">{lv["3"]}</span><span className="text-muted-foreground">{lv["4"]}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user