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 */} -
    -
    - - -
    -
    - -