import Link from "next/link"; import { getFormatter, getTranslations } from "next-intl/server"; import { Pencil, Plus, Trash2, X } from "lucide-react"; import type { Prisma } from "@prisma/client"; import { addAssessment, addContract, addDecision, addEvidence, addNda, approveMaturity, createGateRisk, createSupplier, deleteChild, deleteSupplier, updateSupplier, } from "@/server/actions/suppliers"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Modal } from "@/components/modal"; import { SegmentedRating } from "@/components/segmented-rating"; import { Pill } from "@/components/mockup-ui"; import { SupplierRequirements } from "@/components/supplier-cockpit"; import { supplierRef, withQuery, SUPPLIER_LIFECYCLE_TONE, protectionLevel, PROTECTION_LABEL, buildReqContext, REQUIREMENTS, conformity, computedMaturity, CONFORMITY_TONE, CONFORMITY_LABEL, TARGET_MATURITY, isExpired, isExpiring, } from "@/lib/supplier"; export type SupplierAssetDetail = Prisma.AssetGetPayload<{ 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 } } } }; }; }>; const inputCls = "h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm"; function contextOf(s: SupplierAssetDetail) { const linkedAssets = [ { name: s.name, c: s.confidentiality, i: s.integrity, a: s.availability }, ...s.relationsFrom.map((r) => ({ name: r.relatedAsset.name, c: r.relatedAsset.confidentiality, i: r.relatedAsset.integrity, a: r.relatedAsset.availability })), ...s.relationsTo.map((r) => ({ name: r.asset.name, c: r.asset.confidentiality, i: r.asset.integrity, a: r.asset.availability })), ]; const maxCia = Math.max(...linkedAssets.map((a) => Math.max(a.c, a.i, a.a))); 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 { linkedAssets: linkedAssets.slice(1), maxCia, level, ctx }; } /* ─────────────────────── Cockpit (Detail) ─────────────────────── */ export async function SupplierDetailModal({ supplier, canWrite, backHref = "/suppliers" }: { supplier: SupplierAssetDetail; canWrite: boolean; backHref?: string }) { const t = await getTranslations("suppliers"); const tLife = await getTranslations("supplierStatus"); const tEKind = await getTranslations("evidenceKind"); const tc = await getTranslations("common"); const fmt = await getFormatter(); const date = (d: Date | null) => (d ? fmt.dateTime(d, { dateStyle: "medium" }) : tc("none")); const p = supplier.supplierProfile!; const { linkedAssets, maxCia, level, ctx } = contextOf(supplier); const conf = conformity(level, ctx); const computed = computedMaturity(level, ctx); const reqs = REQUIREMENTS.map((r) => ({ key: r.key, label: r.label, control: r.control, tier: r.tier, theme: r.theme, met: r.met(ctx) })); const editHref = withQuery(backHref, "edit", supplier.id); const gateEvidenceHref = editHref; const gateDecisionHref = editHref; return ( {p.nis2Relevant && NIS2} Schutzbedarf {PROTECTION_LABEL[level]} {CONFORMITY_LABEL[conf]} } closeHref={backHref} closeLabel={t("close")} footer={ <> {canWrite && ( )} } >
{/* Risiken */}

{t("risks")}

{supplier.riskAssets.length === 0 &&

{tc("none")}

}
    {supplier.riskAssets.map((ra) => (
  • R-{String(ra.risk.refNo).padStart(3, "0")} {ra.risk.title}
  • ))}
{/* Verträge & NDA & Nachweise (read) */}

{t("contracts")} & {t("ndas")}

    {supplier.contracts.map((c) => (
  • {c.reference ?? c.type} {c.avDpa && AV/DPA} {c.validTo && {t("validTo")}: {date(c.validTo)}}
  • ))} {supplier.ndas.map((n) => (
  • NDA: {n.subject ?? n.parties ?? ""} {n.validTo && {t("validTo")}: {date(n.validTo)}}
  • ))} {supplier.contracts.length + supplier.ndas.length === 0 &&
  • {tc("none")}
  • }

{t("evidence")}

    {supplier.evidence.map((e) => (
  • {tEKind(e.kind)} {e.name ?? tEKind(e.kind)} {e.adequacyChecked && {t("adequacy")}} {e.validTo && {t("expires")} {date(e.validTo)}}
  • ))} {supplier.evidence.length === 0 &&
  • {tc("none")}
  • }
{/* ISB-Freigabe */} {canWrite && (

{t("isbApproval")}

{t("approvalNote")}

)} {/* Lifecycle + Datenkategorien */}
{t("status")}: {tLife(p.lifecycle)} {p.dataCategories.length > 0 && ` · ${t("dataCategories")}: ${p.dataCategories.join(", ")}`}
); } /* ─────────────────────── Stammdaten-Formular ─────────────────────── */ async function SupplierFields({ supplier, formId }: { supplier?: SupplierAssetDetail; formId?: string }) { const t = await getTranslations("suppliers"); const tLife = await getTranslations("supplierStatus"); const tA = await getTranslations("assets"); const tLevel = await getTranslations("protectionLevel"); const f = formId ? { form: formId } : {}; const p = supplier?.supplierProfile; return (