From 430aaab0245b841001d76e3b2e6c91faee9c2aa5 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 2 Jul 2026 20:00:39 +0200 Subject: [PATCH] =?UTF-8?q?GUI-Bausteine:=20CiaGauge,=20SegmentedRating,?= =?UTF-8?q?=20Prozess-Dialog=20aufger=C3=A4umt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Referenz: docs/ISMS-GUI-Verbesserungen-GEFIM.html - CiaGauge: Schutzbedarf/Schadenshöhe als 4er-Segmentbalken (Farblogik 1–4) statt Zahlen-Quadrate — einheitlich in allen Tabellen/Karten; in Detailkarten mit VER/INT/VFB-Labels - SegmentedRating: 1–4 anklickbare, farbige Stufen-Buttons (Client- Komponente mit Hidden-Input + HTML-form-Association) für Asset- Schutzbedarf und BIA-Schadenshöhe statt Dropdowns - Prozess-Bearbeiten aufgeräumt: Sprungnavigation (Grunddaten/Assets/ BIA), genau EIN Speichern-Button (Stammdaten + BIA in einer Action saveProcessAll, Felder per form-Attribut verbunden), Löschen ins ⋯-Überlaufmenü verschoben; Asset-Zuordnung als entfernbare Chips mit Segmentbalken Verifiziert: kombiniertes Speichern (Name + BIA-Schadenshöhe in einem Vorgang, Kritikalität neu berechnet), SegmentedRating-Wert korrekt übernommen, ⋯-Menü mit Löschen. Co-Authored-By: Claude Opus 4.8 --- messages/de.json | 5 +- messages/en.json | 5 +- src/components/asset-form.tsx | 19 +- src/components/asset-modals.tsx | 2 +- src/components/mockup-ui.tsx | 62 ++++-- src/components/process-modals.tsx | 286 +++++++++++++++++----------- src/components/segmented-rating.tsx | 67 +++++++ src/server/actions/processes.ts | 49 +++++ 8 files changed, 362 insertions(+), 133 deletions(-) create mode 100644 src/components/segmented-rating.tsx diff --git a/messages/de.json b/messages/de.json index 7c574e5..8e99698 100644 --- a/messages/de.json +++ b/messages/de.json @@ -175,7 +175,10 @@ "impactShort": "Schadenshöhe (C/I/A)", "close": "Schließen", "noAssets": "Noch keine Assets zugeordnet.", - "category": "Prozesskategorie" + "category": "Prozesskategorie", + "secMaster": "Grunddaten", + "secAssets": "Zugeordnete Assets", + "deleteProcess": "Prozess löschen" }, "processRole": { "PRIMARY": "Primär", diff --git a/messages/en.json b/messages/en.json index 0494dfb..b89a7d2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -175,7 +175,10 @@ "impactShort": "Damage level (C/I/A)", "close": "Close", "noAssets": "No assets assigned yet.", - "category": "Process category" + "category": "Process category", + "secMaster": "Basic data", + "secAssets": "Assigned assets", + "deleteProcess": "Delete process" }, "processRole": { "PRIMARY": "Primary", diff --git a/src/components/asset-form.tsx b/src/components/asset-form.tsx index 50d186b..a2422b5 100644 --- a/src/components/asset-form.tsx +++ b/src/components/asset-form.tsx @@ -5,10 +5,10 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; +import { SegmentedRating } from "@/components/segmented-rating"; const ASSET_TYPES = ["INFORMATION", "SYSTEM", "APPLICATION", "LOCATION", "SUPPLIER", "PERSON", "DATA"] as const; const ASSET_STATUS = ["ACTIVE", "PLANNED", "RETIRED"] as const; -const LEVELS = [1, 2, 3, 4] as const; /** Formular für Anlegen/Bearbeiten eines Assets (Server-Action wird übergeben). */ export async function AssetForm({ @@ -96,14 +96,15 @@ export async function AssetForm({ ] as const ).map(([name, label, value]) => (
- - + +
+ +
))} diff --git a/src/components/asset-modals.tsx b/src/components/asset-modals.tsx index 1dead85..b36dd59 100644 --- a/src/components/asset-modals.tsx +++ b/src/components/asset-modals.tsx @@ -98,7 +98,7 @@ export async function AssetDetailModal({
{asset.name} - +
{t("owner")}: {asset.owner?.name ?? tc("none")} · {t("type")}: {tType(asset.type)} diff --git a/src/components/mockup-ui.tsx b/src/components/mockup-ui.tsx index fd41e74..f4c3a57 100644 --- a/src/components/mockup-ui.tsx +++ b/src/components/mockup-ui.tsx @@ -55,28 +55,60 @@ export function KpiCard({ ); } -/** Schutzbedarf/Schadenshöhe als drei Quadrate (C/I/A) wie im Mockup. */ -const CIA_COLORS: Record = { +// Segmentbalken-Farben je Stufe 1–4 (Referenz docs/ISMS-GUI-Verbesserungen-GEFIM.html) +const CIA_BAR_COLORS: Record = { 1: "bg-[#9db2c9]", 2: "bg-[#5d8fbf]", 3: "bg-[#e0982e]", 4: "bg-[#d64c4c]", }; -export function CiaBadge({ c, i, a }: { c: number; i: number; a: number }) { +/** Ein Schutzziel als 4er-Segmentbalken, gefüllt bis `level`. */ +function CiaSegment({ level, label }: { level: number; label?: string }) { + const color = CIA_BAR_COLORS[level] ?? CIA_BAR_COLORS[1]; return ( - - {[c, i, a].map((v, idx) => ( - - {v} - - ))} + + + {[1, 2, 3, 4].map((seg) => ( + + ))} + + {label && ( + + {label} + + )} + + ); +} + +/** + * Schutzbedarf / Schadenshöhe als drei Segmentbalken (C/I/A). Mit `labels` + * werden VER/INT/VFB unter den Balken gezeigt (Detailkarten); ohne für + * kompakte Tabellenzellen. + */ +export function CiaBadge({ + c, + i, + a, + labels = false, +}: { + c: number; + i: number; + a: number; + labels?: boolean; +}) { + return ( + + + + ); } diff --git a/src/components/process-modals.tsx b/src/components/process-modals.tsx index 6fcd4b6..09c3cf8 100644 --- a/src/components/process-modals.tsx +++ b/src/components/process-modals.tsx @@ -1,14 +1,13 @@ import Link from "next/link"; import { getTranslations } from "next-intl/server"; -import { Pencil, Trash2, X } from "lucide-react"; +import { MoreHorizontal, Pencil, Trash2, X } from "lucide-react"; import type { Prisma } from "@prisma/client"; import { assignAsset, createProcess, deleteProcess, - saveBia, + saveProcessAll, unassignAsset, - updateProcess, } from "@/server/actions/processes"; import { ProcessForm } from "@/components/process-form"; import { Button } from "@/components/ui/button"; @@ -16,6 +15,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 { SegmentedRating } from "@/components/segmented-rating"; import { CiaBadge, CriticalityPill, Pill, Tag } from "@/components/mockup-ui"; import { riskLevel, riskRef, RISK_PILL_TONE } from "@/lib/risk"; @@ -48,7 +48,6 @@ export type ProcessWithDetail = Prisma.ProcessGetPayload<{ }; }>; -const LEVELS = [1, 2, 3, 4] as const; /** Anlegen als Popup (Stammdaten; Assets/BIA folgen im Bearbeiten-Popup). */ export async function ProcessCreateModal({ @@ -141,6 +140,7 @@ export async function ProcessDetailModal({ c={pa.asset.confidentiality} i={pa.asset.integrity} a={pa.asset.availability} + labels />
@@ -234,7 +234,12 @@ export async function ProcessDetailModal({ ); } -/** Bearbeiten im selben Popup: Stammdaten, Asset-Zuordnung, BIA, Löschen. */ +/** + * Bearbeiten im selben Popup — aufgeräumt (Referenz docs/ISMS-GUI-Verbesserungen): + * Sprungnavigation, genau EIN Speichern-Button (Stammdaten + BIA über die + * HTML-`form`-Association verbunden), Löschen im ⋯-Überlaufmenü. Die + * Asset-Zuordnung bleibt inkrementell (Chips add/remove). + */ export async function ProcessEditModal({ process, users, @@ -247,20 +252,23 @@ export async function ProcessEditModal({ const t = await getTranslations("processes"); const tAssets = await getTranslations("assets"); const tRole = await getTranslations("processRole"); - const tCrit = await getTranslations("criticality"); const tCat = await getTranslations("processCategory"); const tc = await getTranslations("common"); const bia = process.bia; + const FORM_ID = "process-edit-form"; const selectClass = "h-9 rounded-md border border-input bg-transparent px-3 text-sm"; const primary = process.processAssets.filter((pa) => pa.role === "PRIMARY"); const secondary = process.processAssets.filter((pa) => pa.role === "SECONDARY"); - const assetRow = (pa: ProcessWithDetail["processAssets"][number]) => ( -
  • - {pa.asset.name} + const assetChip = (pa: ProcessWithDetail["processAssets"][number]) => ( + + {pa.asset.name} -
    +
    -
  • + ); + const jump = [ + { href: "#p-grunddaten", label: t("secMaster") }, + { href: "#p-assets", label: t("secAssets") }, + { href: "#p-bia", label: t("bia") }, + ]; + return ( + + + +
    +
    + +
    +
    + + } closeHref={`/processes?detail=${process.id}`} closeLabel={t("close")} + footer={ + <> + + + + } > -
    - {/* Stammdaten */} -
    -
    - - -
    -
    - -