GUI-Bausteine: CiaGauge, SegmentedRating, Prozess-Dialog aufgeräumt

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 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 20:00:39 +02:00
co-authored by Claude Opus 4.8
parent 5fb4b3cbf4
commit 430aaab024
8 changed files with 362 additions and 133 deletions
+49
View File
@@ -190,3 +190,52 @@ export async function saveBia(processId: string, formData: FormData) {
});
revalidatePath("/processes");
}
/**
* Kombiniertes Speichern für den aufgeräumten Bearbeiten-Dialog: Stammdaten
* und BIA in einem Vorgang (ein Speichern-Button). Asset-Zuordnung läuft
* weiter über die inkrementellen Aktionen assignAsset/unassignAsset.
*/
export async function saveProcessAll(processId: string, formData: FormData) {
const session = await requireSession();
requirePermission(session, "bia:write");
const db = dbForTenant(session.user.tenantId);
const before = await db.process.findUnique({
where: { id: processId },
include: { bia: true },
});
if (!before) throw new Error("Prozess nicht gefunden");
const master = parseProcessForm(formData);
await db.process.update({ where: { id: processId }, data: master });
const level = z.coerce.number().int().min(1).max(4);
const biaData = {
rtoHours: hours.parse(formData.get("rtoHours") ?? ""),
rpoHours: hours.parse(formData.get("rpoHours") ?? ""),
mtdHours: hours.parse(formData.get("mtdHours") ?? ""),
impactC: level.parse(formData.get("impactC")),
impactI: level.parse(formData.get("impactI")),
impactA: level.parse(formData.get("impactA")),
notes: (formData.get("notes") as string)?.trim() || null,
};
const criticality = Math.max(biaData.impactC, biaData.impactI, biaData.impactA);
await db.biaEntry.upsert({
where: { processId },
update: { ...biaData, criticality },
create: { ...biaData, criticality, processId, tenantId: session.user.tenantId },
});
await writeAuditLog({
tenantId: session.user.tenantId,
actorId: session.user.id,
action: "update",
entity: "process",
entityId: processId,
before,
after: { ...master, bia: { ...biaData, criticality } },
});
revalidatePath("/processes");
redirect(`/processes?detail=${processId}`);
}