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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user