import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { Plus } from "lucide-react"; import { requireSession } from "@/server/auth"; import { dbForTenant } from "@/server/db"; import { hasPermission, requirePermission } from "@/server/rbac"; import { Button } from "@/components/ui/button"; import { PageHead, Pill, CriticalityPill, CiaBadge } from "@/components/mockup-ui"; import { FilterTabs } from "@/components/filter-tabs"; import { SupplierCreateModal, SupplierDetailModal, SupplierEditModal, type SupplierAssetDetail, } from "@/components/supplier-modals"; import { ServiceCreateModal, ServiceDetailModal, ServiceEditModal, type ServiceAssetDetail, } from "@/components/service-modals"; import { supplierRef, serviceRef, protectionLevel, PROTECTION_LABEL, buildReqContext, conformity, computedMaturity, CONFORMITY_TONE, CONFORMITY_LABEL, TARGET_MATURITY, } from "@/lib/supplier"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; const SUPPLIER_INCLUDE = { supplierProfile: true, contracts: true, ndas: true, evidence: true, assessments: true, subcontractors: true, decisions: true, maturity: true, riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true, score: true } } } }, relationsFrom: { include: { relatedAsset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } }, relationsTo: { include: { asset: { select: { name: true, confidentiality: true, integrity: true, availability: true } } } }, } as const; const SERVICE_INCLUDE = { serviceProfile: { include: { provider: { select: { id: true, name: true } } } }, raci: true, riskAssets: { include: { risk: { select: { id: true, refNo: true, title: true } } } }, relationsFrom: { include: { relatedAsset: { select: { id: true, name: true } } } }, relationsTo: { include: { asset: { select: { id: true, name: true } } } }, processAssets: { include: { process: { select: { id: true, name: true } } } }, } as const; export default async function SuppliersPage({ searchParams, }: { searchParams: Promise<{ tab?: string; detail?: string; edit?: string; new?: string }>; }) { const session = await requireSession(); requirePermission(session, "supplier:read"); const t = await getTranslations("suppliers"); const ts = await getTranslations("services"); const tCrit = await getTranslations("criticality"); const tc = await getTranslations("common"); const params = await searchParams; const isServices = params.tab === "services"; const db = dbForTenant(session.user.tenantId); const canWrite = hasPermission(session, "supplier:write"); const tabs = [ { href: "/suppliers", label: ts("tabSuppliers"), active: !isServices }, { href: "/suppliers?tab=services", label: ts("tabServices"), active: isServices }, ]; const head = ( }> {isServices ? ts("newService") : t("newSupplier")} ) } /> ); /* ── IT-Services ── */ if (isServices) { const services = (await db.asset.findMany({ where: { type: "IT_SERVICE", serviceProfile: { isNot: null } }, include: SERVICE_INCLUDE, orderBy: { serviceProfile: { refNo: "asc" } }, take: 300, })) as ServiceAssetDetail[]; const providers = await db.asset.findMany({ where: { type: "SUPPLIER", supplierProfile: { isNot: null } }, select: { id: true, name: true }, orderBy: { name: "asc" }, }); const modalId = params.edit && canWrite ? params.edit : params.detail; const modalService = modalId ? ((await db.asset.findUnique({ where: { id: modalId }, include: SERVICE_INCLUDE })) as ServiceAssetDetail | null) : null; return (
{head}
{ts("ref")} {ts("name")} {ts("provider")} {ts("protection")} {ts("criticality")} {ts("raciCoverage")} {services.length === 0 && ( {ts("empty")} )} {services.map((s) => ( {serviceRef(s.serviceProfile!.refNo)} {s.name} {s.serviceProfile?.provider?.name ?? (s.serviceProfile?.internal ? ts("internal") : tc("none"))} {s.raci.length} ))}
{modalService && params.edit && canWrite ? ( ) : modalService ? ( ) : params.new && canWrite ? ( ) : null}
); } /* ── Lieferanten ── */ const suppliers = await db.asset.findMany({ where: { type: "SUPPLIER", supplierProfile: { isNot: null } }, include: SUPPLIER_INCLUDE, orderBy: { supplierProfile: { refNo: "asc" } }, take: 300, }); const rows = suppliers.map((s) => { const cias = [ Math.max(s.confidentiality, s.integrity, s.availability), ...s.relationsFrom.map((r) => Math.max(r.relatedAsset.confidentiality, r.relatedAsset.integrity, r.relatedAsset.availability)), ...s.relationsTo.map((r) => Math.max(r.asset.confidentiality, r.asset.integrity, r.asset.availability)), ]; const maxCia = Math.max(...cias); const level = protectionLevel(maxCia); const ctx = buildReqContext({ contracts: s.contracts, ndas: s.ndas, evidence: s.evidence, assessments: s.assessments, subcontractors: s.subcontractors, nextReview: s.supplierProfile?.nextReview ?? null, linkedRiskCount: s.riskAssets.length, decisionCount: s.decisions.length, }); return { s, level, conf: conformity(level, ctx), maturity: s.maturity?.isbValue ?? computedMaturity(level, ctx), linked: [...s.relationsFrom.map((r) => r.relatedAsset.name), ...s.relationsTo.map((r) => r.asset.name)], }; }); const modalId = params.edit && canWrite ? params.edit : params.detail; const modalSupplier = modalId ? ((await db.asset.findUnique({ where: { id: modalId }, include: SUPPLIER_INCLUDE })) as SupplierAssetDetail | null) : null; return (
{head}
{t("ref")} {t("name")} {t("linkedAssets")} {t("derivedLevel")} {t("maturity")} {t("conformity")} {rows.length === 0 && ( {t("empty")} )} {rows.map(({ s, level, conf, maturity, linked }) => ( {supplierRef(s.supplierProfile!.refNo)} {s.name} {s.supplierProfile?.sector &&
{s.supplierProfile.sector}
}
{linked.join(", ") || tc("none")} Schutzbedarf {PROTECTION_LABEL[level]} {maturity.toFixed(1)} / {TARGET_MATURITY.toFixed(1)} {CONFORMITY_LABEL[conf]}
))}
{modalSupplier && params.edit && canWrite ? ( ) : modalSupplier ? ( ) : params.new && canWrite ? ( ) : null}
); }