Iteration 2: Assets & BIA als gemeinsames Modul

- App-Shell mit Seitennavigation (alle 12 Module, kommende ausgegraut),
  shadcn/ui-Setup (Base-UI-Variante) mit hellem Theme
- Asset-Inventar: filterbare Liste (Suche/Typ/Status), Anlegen/Bearbeiten/
  Löschen, Detailansicht mit Schutzbedarf (C/I/A 1–4), Abhängigkeiten
  (beide Richtungen, hinzufügen/entfernen), zugeordneten Prozessen mit
  Primär-/Sekundär-Rolle und Platzhalter für zugeordnete Risiken
- Prozesse & BIA: Liste mit Kritikalität/RTO/MTD, Prozess-Detail mit
  Asset-Zuordnung nach Rolle (primär/sekundär), BIA-Formular
  (RTO/RPO/MTD, Schadenshöhe je Schutzziel, Kritikalität nach Max-Prinzip)
- Server-Actions mit Zod-Validierung, requirePermission und Audit-Log
  für jede schreibende Aktion; Tenant-Guard um upsert-Injektion erweitert
- Prisma: Asset, AssetRelation, Process, ProcessAsset, BiaEntry
  inkl. RLS-Policies; Seed mit Beispiel-Assets, Prozessen und BIA
- Im Browser verifiziert: CRUD, Relationen, BIA-Speichern, Audit-Einträge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 12:35:19 +02:00
co-authored by Claude Fable 5
parent 880afed391
commit 392bb246cf
41 changed files with 6085 additions and 149 deletions
+91
View File
@@ -0,0 +1,91 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import {
LayoutDashboard,
Boxes,
ShieldAlert,
ClipboardCheck,
KanbanSquare,
Siren,
BookOpenText,
MessagesSquare,
Network,
FolderCheck,
Truck,
LineChart,
} from "lucide-react";
import { auth, signOut } from "@/server/auth";
import { Button } from "@/components/ui/button";
import { NavLink } from "@/components/nav-link";
export default async function AppLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
const session = await auth();
if (!session?.user) redirect("/login");
const t = await getTranslations("nav");
const tc = await getTranslations("common");
const nav = [
{ href: "/dashboard", label: t("dashboard"), icon: LayoutDashboard, enabled: true },
{ href: "/assets", label: t("assetsBia"), icon: Boxes, enabled: true, match: ["/assets", "/processes"] },
{ href: "/risks", label: t("risks"), icon: ShieldAlert, enabled: false },
{ href: "/soa", label: t("soa"), icon: ClipboardCheck, enabled: false },
{ href: "/measures", label: t("measures"), icon: KanbanSquare, enabled: false },
{ href: "/incidents", label: t("incidents"), icon: Siren, enabled: false },
{ href: "/policies", label: t("policies"), icon: BookOpenText, enabled: false },
{ href: "/chat", label: t("chat"), icon: MessagesSquare, enabled: false },
{ href: "/dependencies", label: t("dependencies"), icon: Network, enabled: false },
{ href: "/evidence", label: t("evidence"), icon: FolderCheck, enabled: false },
{ href: "/suppliers", label: t("suppliers"), icon: Truck, enabled: false },
{ href: "/review", label: t("review"), icon: LineChart, enabled: false },
];
async function logout() {
"use server";
await signOut({ redirectTo: "/login" });
}
return (
<div className="flex min-h-screen">
<aside className="flex w-60 shrink-0 flex-col border-r bg-sidebar">
<div className="flex h-14 items-center border-b px-4">
<Link href="/dashboard" className="font-semibold tracking-tight">
{tc("appName")}
</Link>
</div>
<nav className="flex-1 space-y-0.5 overflow-y-auto p-2">
{nav.map((item) =>
item.enabled ? (
<NavLink key={item.href} href={item.href} match={item.match}>
<item.icon className="size-4" />
{item.label}
</NavLink>
) : (
<span
key={item.href}
title={tc("comingSoon")}
className="flex cursor-not-allowed items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground/50"
>
<item.icon className="size-4" />
{item.label}
</span>
)
)}
</nav>
<div className="border-t p-3 text-xs text-muted-foreground">
<p className="truncate font-medium text-foreground">{session.user.name}</p>
<p className="truncate">{session.user.tenantSlug}</p>
<form action={logout} className="mt-2">
<Button type="submit" variant="outline" size="sm" className="w-full">
{tc("logout")}
</Button>
</form>
</div>
</aside>
<div className="flex min-w-0 flex-1 flex-col">{children}</div>
</div>
);
}