import Link from "next/link"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { Plus, Search } from "lucide-react"; import type { AssetType } from "@prisma/client"; import { requireSession } from "@/server/auth"; import { dbForTenant } from "@/server/db"; import { hasPermission, requirePermission } from "@/server/rbac"; import { Button } from "@/components/ui/button"; import { FilterTabs } from "@/components/filter-tabs"; import { CiaBadge, CiaLegend, KpiCard, OwnerChip, PageHead, Pill, Tag, } from "@/components/mockup-ui"; import { AssetCreateModal, AssetDetailModal, AssetEditModal, } from "@/components/asset-modals"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; const ASSET_TYPES = ["INFORMATION", "SYSTEM", "APPLICATION", "LOCATION", "SUPPLIER", "IT_SERVICE", "PERSON", "DATA"] as const; const STATUS_TONE = { ACTIVE: "ok", PLANNED: "info", RETIRED: "mut" } as const; /** * Lieferanten- und IT-Service-Assets werden in ihrem Fach-Cockpit geöffnet * (sofern ein Fachprofil existiert), alle übrigen Assets in der normalen * Asset-Detailansicht. */ function assetDetailHref(asset: { id: string; type: AssetType; supplierProfile: { id: string } | null; serviceProfile: { id: string } | null; }) { if (asset.type === "SUPPLIER" && asset.supplierProfile) return `/suppliers?detail=${asset.id}`; if (asset.type === "IT_SERVICE" && asset.serviceProfile) return `/suppliers?tab=services&detail=${asset.id}`; return `/assets?detail=${asset.id}`; } export default async function AssetsPage({ searchParams, }: { searchParams: Promise<{ q?: string; type?: string; detail?: string; edit?: string; new?: string }>; }) { const session = await requireSession(); requirePermission(session, "asset:read"); const t = await getTranslations("assets"); const tType = await getTranslations("assetType"); const tStatus = await getTranslations("assetStatus"); const tc = await getTranslations("common"); const params = await searchParams; const { q, type } = params; const activeType = ASSET_TYPES.includes(type as AssetType) ? (type as AssetType) : null; const db = dbForTenant(session.user.tenantId); const [assets, allAssets] = await Promise.all([ db.asset.findMany({ where: { ...(q ? { name: { contains: q, mode: "insensitive" } } : {}), ...(activeType ? { type: activeType } : {}), }, include: { owner: { select: { name: true } }, supplierProfile: { select: { id: true } }, serviceProfile: { select: { id: true } }, relationsFrom: { include: { relatedAsset: { select: { name: true } } }, take: 4, }, }, orderBy: { name: "asc" }, take: 200, }), db.asset.findMany({ select: { type: true, ownerId: true, confidentiality: true, integrity: true, availability: true }, }), ]); const total = allAssets.length; const typeCount = new Set(allAssets.map((a) => a.type)).size; const highProtection = allAssets.filter( (a) => Math.max(a.confidentiality, a.integrity, a.availability) >= 3 ).length; const withoutOwner = allAssets.filter((a) => !a.ownerId).length; const suppliers = allAssets.filter((a) => a.type === "SUPPLIER").length; const canWrite = hasPermission(session, "asset:write"); // Popup-Zustand aus searchParams: ?detail= (read-only), ?edit=, ?new=1 const modalId = params.edit && canWrite ? params.edit : params.detail; const modalAsset = modalId ? await db.asset.findUnique({ where: { id: modalId }, include: { owner: { select: { name: true } }, supplierProfile: { select: { id: true } }, serviceProfile: { select: { id: true } }, relationsFrom: { include: { relatedAsset: { select: { id: true, name: true, type: true, confidentiality: true, integrity: true, availability: true, }, }, }, }, relationsTo: { include: { asset: { select: { id: true, name: true } } } }, processAssets: { include: { process: { select: { id: true, name: true } } } }, riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } }, orderBy: { risk: { score: "desc" } }, }, }, }) : null; // Lieferanten/IT-Services im Fach-Cockpit öffnen, auch bei direktem Aufruf // (nur wenn ein Fachprofil existiert; profillose Alt-Assets bleiben im Asset-Detail) if (modalAsset && ((modalAsset.type === "SUPPLIER" && modalAsset.supplierProfile) || (modalAsset.type === "IT_SERVICE" && modalAsset.serviceProfile))) { const base = modalAsset.type === "IT_SERVICE" ? "/suppliers?tab=services" : "/suppliers"; redirect(`${base}${base.includes("?") ? "&" : "?"}${params.edit && canWrite ? "edit" : "detail"}=${modalAsset.id}`); } const users = canWrite && (params.edit || params.new) ? await db.user.findMany({ where: { status: "ACTIVE" }, select: { id: true, name: true }, orderBy: { name: "asc" }, }) : []; const otherAssets = canWrite && params.edit && modalAsset ? await db.asset.findMany({ where: { id: { not: modalAsset.id } }, select: { id: true, name: true }, orderBy: { name: "asc" }, }) : []; const typeHref = (v: string | null) => `/assets${v ? `?type=${v}` : ""}${q ? `${v ? "&" : "?"}q=${encodeURIComponent(q)}` : ""}`; return (
{canWrite && ( )} } />
0 ? t("kpiNoOwnerTrend") : undefined} trendColor="warn" />
({ href: typeHref(v), label: tType(v), active: activeType === v, })), ]} />
{activeType && }
Asset {t("type")} {t("owner")} {t("protection")} {t("dependencies")} {t("status")} {assets.length === 0 && ( {t("empty")} )} {assets.map((asset) => ( {asset.name} {tType(asset.type)} {asset.relationsFrom.length ? asset.relationsFrom.map((r) => r.relatedAsset.name).join(", ") : tc("none")} {tStatus(asset.status)} ))}
{modalAsset && params.edit && canWrite ? ( ) : modalAsset ? ( ) : params.new && canWrite ? ( ) : null}
); }