Feinschliff Risiko/Bewertung: Ampelfarben, Titel, Skala, Legende

- Detail-Popups einheitlich: Name/Bezeichner steht immer im Titel
  (Risiko "R-001 · <Name>", Maßnahme "M-001 · <Name>", Prozess <Name>)
- Bewertungsbalken (Schutzbedarf/Schadenshöhe) + Score-Pillen als
  einheitliche Ampel: 1 grün, 2 gelb, 3 orange, 4 rot; Risiko-Pillen
  folgen exakt der Heatmap-Skala (medium = gelb statt blau, elevated =
  orange) — Restrisiko-Pille jetzt korrekt gelb
- Risiko-Detailkarte: Farbskala von der Mitte nach oben verschoben;
  alle Detail-/Bearbeiten-Popups etwas breiter (max-w-4xl)
- CiaGauge-Beschriftung vereinheitlicht: VER/INT/VFB erscheint einmal
  pro Tabelle als Spaltenüberschrift (Asset-Liste + Popup-Tabellen),
  keine Labels mehr pro Zeile; Standalone-Gauges in Detailkarten
  behalten die Labels

Verifiziert: alle vier Ampelfarben im DOM, Restrisiko-Pille gelb,
Legende in Tabellenköpfen, Titel mit Name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 20:28:48 +02:00
co-authored by Claude Opus 4.8
parent 430aaab024
commit 1b13af46db
11 changed files with 76 additions and 29 deletions
+7 -1
View File
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { FilterTabs } from "@/components/filter-tabs";
import {
CiaBadge,
CiaLegend,
KpiCard,
OwnerChip,
PageHead,
@@ -196,7 +197,12 @@ export default async function AssetsPage({
<TableHead>Asset</TableHead>
<TableHead>{t("type")}</TableHead>
<TableHead>{t("owner")}</TableHead>
<TableHead>{t("protection")} (C/I/A)</TableHead>
<TableHead>
<span className="flex flex-col gap-1">
{t("protection")}
<CiaLegend />
</span>
</TableHead>
<TableHead>{t("dependencies")}</TableHead>
<TableHead>{t("status")}</TableHead>
</TableRow>
+10 -1
View File
@@ -12,7 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Modal } from "@/components/modal";
import { AssetForm } from "@/components/asset-form";
import { CiaBadge, Pill, Tag } from "@/components/mockup-ui";
import { CiaBadge, CiaLegend, Pill, Tag } from "@/components/mockup-ui";
import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk";
export type AssetWithRelations = Prisma.AssetGetPayload<{
@@ -145,6 +145,15 @@ export async function AssetDetailModal({
<p className="text-sm text-muted-foreground">{tc("none")}</p>
)}
<table className="w-full text-sm">
<thead>
<tr>
<th />
<th />
<th className="pb-1 text-right font-normal">
<CiaLegend />
</th>
</tr>
</thead>
<tbody>
{asset.relationsFrom.map((rel) => (
<tr key={rel.id} className="border-b last:border-0">
+1 -1
View File
@@ -42,7 +42,7 @@ export async function MeasureDetailModal({
return (
<Modal
title={t("detailHeading", { ref: measureRef(measure.refNo) })}
title={t("detailHeading", { ref: measureRef(measure.refNo), name: measure.title })}
sub={t("detailSub")}
headerExtra={
<span className="flex items-center gap-2">
+18 -4
View File
@@ -55,11 +55,11 @@ export function KpiCard({
);
}
// Segmentbalken-Farben je Stufe 14 (Referenz docs/ISMS-GUI-Verbesserungen-GEFIM.html)
// Segmentbalken-Farben je Stufe 14 als Ampel: Grün → Gelb → Orange → Rot
const CIA_BAR_COLORS: Record<number, string> = {
1: "bg-[#9db2c9]",
2: "bg-[#5d8fbf]",
3: "bg-[#e0982e]",
1: "bg-[#2e9e6b]",
2: "bg-[#e5b000]",
3: "bg-[#e07d2e]",
4: "bg-[#d64c4c]",
};
@@ -113,6 +113,19 @@ export function CiaBadge({
);
}
/** VER/INT/VFB-Beschriftung passend zur Balkenbreite — einmal pro Tabelle als Überschrift. */
export function CiaLegend() {
return (
<span className="inline-flex items-center gap-2.5 text-[9.5px] font-bold tracking-[.04em] text-muted-foreground">
{["VER", "INT", "VFB"].map((l) => (
<span key={l} className="w-[34px] text-center">
{l}
</span>
))}
</span>
);
}
export function Tag({ children }: { children: React.ReactNode }) {
return (
<span className="inline-block rounded-md bg-[#eef0f4] px-2 py-0.5 text-[11px] font-bold text-[#555]">
@@ -142,6 +155,7 @@ export function OwnerChip({ name, noOwnerLabel }: { name?: string | null; noOwne
const PILL_TONES = {
ok: "bg-[#e7f6ee] text-[#2e9e6b]",
warn: "bg-[#fbf1de] text-[#a9760f]",
orange: "bg-[#fbdcc0] text-[#c2681a]",
risk: "bg-[#fbe7e7] text-[#d64c4c]",
info: "bg-[#e7f1fa] text-[#3a86c8]",
mut: "bg-[#eef0f4] text-muted-foreground",
+1 -1
View File
@@ -26,7 +26,7 @@ export function Modal({
}) {
return (
<div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/30 p-6 backdrop-blur-[2px]">
<div className="shadow-card my-auto w-full max-w-3xl rounded-2xl border bg-card">
<div className="shadow-card my-auto w-full max-w-4xl rounded-2xl border bg-card">
<div className="flex items-start justify-between gap-3 border-b p-5">
<SectTitle title={title} sub={sub} />
<div className="flex items-center gap-3">
+10 -1
View File
@@ -16,7 +16,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Modal } from "@/components/modal";
import { SegmentedRating } from "@/components/segmented-rating";
import { CiaBadge, CriticalityPill, Pill, Tag } from "@/components/mockup-ui";
import { CiaBadge, CiaLegend, CriticalityPill, Pill, Tag } from "@/components/mockup-ui";
import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk";
export type LinkedRisk = {
@@ -158,6 +158,15 @@ export async function ProcessDetailModal({
</div>
{secondary.length === 0 && <p className="text-sm text-muted-foreground">{t("noAssets")}</p>}
<table className="w-full text-sm">
<thead>
<tr>
<th />
<th />
<th className="pb-1 text-right font-normal">
<CiaLegend />
</th>
</tr>
</thead>
<tbody>
{secondary.map((pa) => (
<tr key={pa.id} className="border-b last:border-0">
+15 -6
View File
@@ -19,7 +19,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Modal } from "@/components/modal";
import { CiaBadge, Pill, Tag } from "@/components/mockup-ui";
import { CiaBadge, CiaLegend, Pill, Tag } from "@/components/mockup-ui";
import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk";
import { measureRef, MEASURE_STATUS_TONE } from "@/lib/measure";
@@ -123,7 +123,7 @@ function RiskScoreScale({ gross, residual }: { gross: number; residual: number |
// 5×5 → Score 1..25; Position linear auf der Skala
const pos = (v: number) => `${((Math.min(25, Math.max(1, v)) - 1) / 24) * 100}%`;
return (
<div className="mt-4">
<div className="pt-1">
<div
className="relative h-3 rounded-lg"
style={{
@@ -173,7 +173,7 @@ export async function RiskDetailModal({
return (
<Modal
title={t("detailHeading", { ref: riskRef(risk.refNo) })}
title={t("detailHeading", { ref: riskRef(risk.refNo), name: risk.title })}
sub={t("detailSub")}
headerExtra={
<span className="flex items-center gap-2">
@@ -241,6 +241,15 @@ export async function RiskDetailModal({
<p className="text-sm text-muted-foreground">{t("noAssets")}</p>
)}
<table className="w-full text-sm">
<thead>
<tr>
<th />
<th />
<th className="pb-1 text-right font-normal">
<CiaLegend />
</th>
</tr>
</thead>
<tbody>
{risk.riskAssets.map((ra) => (
<tr key={ra.id} className="border-b last:border-0">
@@ -266,9 +275,10 @@ export async function RiskDetailModal({
</div>
</div>
{/* Brutto → Rest-Risiko: zwei Karten mit Pfeil + Farbskala */}
{/* Brutto → Rest-Risiko: Farbskala oben, darunter zwei Karten mit Pfeil */}
<div className="mx-5 rounded-xl border border-[#e6e2f3] bg-[#f2f0f9] p-4">
<div className="grid items-center gap-3 sm:grid-cols-[1fr_auto_1fr]">
<RiskScoreScale gross={risk.score} residual={risk.residualScore} />
<div className="mt-4 grid items-center gap-3 sm:grid-cols-[1fr_auto_1fr]">
<RatingCard
title={t("gross")}
likelihood={risk.likelihood}
@@ -285,7 +295,6 @@ export async function RiskDetailModal({
accent="residual"
/>
</div>
<RiskScoreScale gross={risk.score} residual={risk.residualScore} />
</div>
{/* Notwendige Maßnahmen — echt verknüpft */}
+4 -4
View File
@@ -3,11 +3,11 @@
import { useState } from "react";
import { cn } from "@/lib/utils";
// Farblogik Stufe 14 (Referenz docs/ISMS-GUI-Verbesserungen-GEFIM.html)
// Ampel-Farblogik Stufe 14: Grün → Gelb → Orange → Rot
const LEVEL_STYLE: Record<number, { on: string; border: string }> = {
1: { on: "bg-[#9db2c9] text-white border-transparent", border: "border-[#9db2c9]" },
2: { on: "bg-[#5d8fbf] text-white border-transparent", border: "border-[#5d8fbf]" },
3: { on: "bg-[#e0982e] text-white border-transparent", border: "border-[#e0982e]" },
1: { on: "bg-[#2e9e6b] text-white border-transparent", border: "border-[#2e9e6b]" },
2: { on: "bg-[#e5b000] text-[#3b3b3a] border-transparent", border: "border-[#e5b000]" },
3: { on: "bg-[#e07d2e] text-white border-transparent", border: "border-[#e07d2e]" },
4: { on: "bg-[#d64c4c] text-white border-transparent", border: "border-[#d64c4c]" },
};
+4 -4
View File
@@ -19,11 +19,11 @@ export const RISK_CELL_COLORS: Record<RiskLevelKey, string> = {
critical: "#ef9a98",
};
/** Pillen-Ton je Stufe (mockup-ui Pill). */
export const RISK_PILL_TONE: Record<RiskLevelKey, "ok" | "info" | "warn" | "risk"> = {
/** Pillen-Ton je Stufe — gemäß Heatmap-Skala: Grün → Gelb → Orange → Rot. */
export const RISK_PILL_TONE: Record<RiskLevelKey, "ok" | "warn" | "orange" | "risk"> = {
low: "ok",
medium: "info",
elevated: "warn",
medium: "warn",
elevated: "orange",
high: "risk",
critical: "risk",
};