Iteration 3: Risikoanalyse + Menü-Trennung + Prozesskategorie

- Risiko-Modul (SPEC §4.2): Risk/RiskAsset-Schema mit RLS, laufende
  Nummer je Mandant (R-001), Brutto-/Rest-Bewertung (5×5), Behandlung,
  Status, Owner- und Prozess-Bezug, n:m betroffene Assets
- /risks im Mockup-Layout: 5×5-Heatmap (Farb-Bänder + R-Chips, Legende,
  Achsen) und Risikoregister mit Score-Pillen, Behandlungs-Tags, Status
- Risiko-Popups nach App-Muster: Read-only-Detail (Bewertungs-Band
  Brutto vs. Rest, betroffene Assets, Maßnahmen-Platzhalter für
  Iteration 4), Bearbeiten im Popup (Bewertung, Assets verknüpfen,
  Löschen), Anlegen; Server-Actions mit Zod, RBAC und Audit-Log
- Menü aufgetrennt: eigene Punkte "Asset-Inventar", "Business Impact
  Analyse" und "Risikoanalyse" (Modul-Tabs entfernt)
- Prozesskategorie (Kernprozess/Managementprozess/Unterstützender
  Prozess): Schema-Feld, Formulare, Listen-Spalte, Popup-Tag
- Asset-Detail-Popup optisch ans Prozess-Popup angeglichen (Stammdaten-
  Karte mit violettem Rand, Abhängigkeiten-Tabelle, Risiken-Band)
- "Zugeordnete Risiken" jetzt echt: im Asset-Popup (verknüpfte Risiken)
  und im Prozess-Popup (direkt + über Assets aggregiert); Dashboard-KPI
  "Offene Risiken" mit echten Zahlen; Seed mit 4 Beispiel-Risiken

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 15:43:25 +02:00
co-authored by Claude Fable 5
parent c5a1f0095b
commit df634e356d
20 changed files with 1633 additions and 106 deletions
+22 -11
View File
@@ -5,8 +5,7 @@ import { requireSession } from "@/server/auth";
import { dbForTenant } from "@/server/db";
import { hasPermission, requirePermission } from "@/server/rbac";
import { Button } from "@/components/ui/button";
import { ModuleTabs } from "@/components/module-tabs";
import { CriticalityPill, PageHead, SectTitle } from "@/components/mockup-ui";
import { CriticalityPill, PageHead, SectTitle, Tag } from "@/components/mockup-ui";
import {
ProcessCreateModal,
ProcessDetailModal,
@@ -51,6 +50,7 @@ export default async function ProcessesPage({
const t = await getTranslations("processes");
const tAssets = await getTranslations("assets");
const tCrit = await getTranslations("criticality");
const tCat = await getTranslations("processCategory");
const tc = await getTranslations("common");
const params = await searchParams;
@@ -80,6 +80,20 @@ export default async function ProcessesPage({
orderBy: { name: "asc" },
})
: [];
// Zugeordnete Risiken: direkt am Prozess ODER an einem seiner Assets
const modalRisks = modalProcess
? await db.risk.findMany({
where: {
OR: [
{ processId: modalProcess.id },
{ riskAssets: { some: { assetId: { in: modalProcess.processAssets.map((pa) => pa.assetId) } } } },
],
},
select: { id: true, refNo: true, title: true, score: true },
orderBy: { score: "desc" },
})
: [];
const availableAssets =
canWrite && params.edit && modalProcess
? await db.asset.findMany({
@@ -111,13 +125,6 @@ export default async function ProcessesPage({
}
/>
<ModuleTabs
active="/processes"
tabs={[
{ href: "/assets", label: tAssets("tabAssets") },
{ href: "/processes", label: tAssets("tabProcesses") },
]}
/>
<div className="shadow-card mt-4 rounded-xl border bg-card">
<div className="p-4 pb-0">
@@ -127,6 +134,7 @@ export default async function ProcessesPage({
<TableHeader>
<TableRow>
<TableHead>{t("process")}</TableHead>
<TableHead>{t("category")}</TableHead>
<TableHead>{t("owner")}</TableHead>
<TableHead>RTO</TableHead>
<TableHead>RPO</TableHead>
@@ -137,7 +145,7 @@ export default async function ProcessesPage({
<TableBody>
{processes.length === 0 && (
<TableRow>
<TableCell colSpan={6} className="py-8 text-center text-muted-foreground">
<TableCell colSpan={7} className="py-8 text-center text-muted-foreground">
{t("empty")}
</TableCell>
</TableRow>
@@ -149,6 +157,9 @@ export default async function ProcessesPage({
{process.name}
</Link>
</TableCell>
<TableCell>
<Tag>{tCat(process.category)}</Tag>
</TableCell>
<TableCell className="text-muted-foreground">
{process.owner?.name ?? tc("none")}
</TableCell>
@@ -174,7 +185,7 @@ export default async function ProcessesPage({
{modalProcess && params.edit && canWrite ? (
<ProcessEditModal process={modalProcess} users={users} availableAssets={availableAssets} />
) : modalProcess ? (
<ProcessDetailModal process={modalProcess} canWrite={canWrite} />
<ProcessDetailModal process={modalProcess} risks={modalRisks} canWrite={canWrite} />
) : params.new && canWrite ? (
<ProcessCreateModal users={users} />
) : null}