Files
craftvia/src/app/(app)/audit-readiness/[auditId]/export/page.tsx
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

91 lines
4.1 KiB
TypeScript

import Link from "next/link";
import { notFound } from "next/navigation";
import { Download, ListChecks } from "lucide-react";
import { requireSession } from "@/server/auth";
import { dbForTenant } from "@/server/db";
import { PageHead, Pill } from "@/components/mockup-ui";
import { Button } from "@/components/ui/button";
import { buildAbgabeRows } from "@/server/export-context";
import { PRUEFZIEL_LABEL } from "@/lib/export/vda-isa";
/**
* ABGABE-Vorschau (VDA-ISA-Prüfungsdokumentation) — Seite des Audit-Wizards.
* Zeigt je Control den zusammengesetzten „Beschreibung der Umsetzung"-Text
* (übernommene ControlDescription je Anforderung), den bestätigten Reifegrad
* und die Referenz-Dokumentation. Download über die geteilte Export-Route als CSV
* (`?format=abgabe`) oder als echte xlsx (`?format=abgabe-xlsx`).
*/
export default async function AbgabePreviewPage({ params }: { params: Promise<{ auditId: string }> }) {
const session = await requireSession();
const { auditId } = await params;
const db = dbForTenant(session.user.tenantId);
const audit = await db.audit.findFirst({ where: { id: auditId } });
if (!audit) notFound();
const rows = await buildAbgabeRows(db, session.user.tenantId);
const withText = rows.filter((r) => r.umsetzung).length;
return (
<main className="flex-1 p-6">
<PageHead
crumb={`Audit · ${audit.title}`}
title="ABGABE-Vorschau (VDA ISA)"
sub={`${withText} von ${rows.length} Controls mit übernommener Umsetzungsbeschreibung.`}
actions={
<>
<Button variant="outline" size="sm" nativeButton={false} render={<Link href={`/audit-readiness/${auditId}/controls`} />}>
<ListChecks className="size-3.5" /> Zu den Beschreibungen
</Button>
{/* Datei-Download über einen Route-Handler (kein Page-Route) — bewusst `<a>`, nicht `<Link>`. */}
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<Button variant="outline" size="sm" nativeButton={false} render={<a href="/audit-readiness/export?format=abgabe" />}>
<Download className="size-3.5" /> ABGABE (CSV)
</Button>
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<Button variant="outline" size="sm" nativeButton={false} render={<a href="/audit-readiness/export?format=abgabe-xlsx" />}>
<Download className="size-3.5" /> ABGABE (xlsx)
</Button>
</>
}
/>
{rows.length === 0 ? (
<p className="text-[12.5px] text-muted-foreground">Keine Controls vorhanden.</p>
) : (
<div className="space-y-3">
{rows.map((r) => (
<section key={r.control} className="shadow-card rounded-xl border bg-card p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<h2 className="font-heading text-sm font-semibold">{r.control} · {r.frage}</h2>
<div className="flex items-center gap-2">
<Pill tone="info">{PRUEFZIEL_LABEL[r.pruefziel]}</Pill>
<Pill tone={r.reifegrad === null ? "mut" : "ok"}>
Reifegrad {r.reifegrad ?? "—"}{r.zielReifegrad !== null ? ` / Ziel ${r.zielReifegrad}` : ""}
</Pill>
</div>
</div>
{r.umsetzung ? (
<p className="mt-2 text-[12.5px] whitespace-pre-wrap">{r.umsetzung}</p>
) : (
<p className="mt-2 text-[12px] text-muted-foreground">Noch keine übernommene Beschreibung — im Beschreibungs-Schritt erzeugen und übernehmen.</p>
)}
{r.referenzen && (
<p className="mt-2 border-t pt-2 text-[11.5px] text-muted-foreground">
<span className="font-semibold">Referenz Dokumentation:</span> {r.referenzen}
</p>
)}
</section>
))}
</div>
)}
<p className="mt-4 text-[11.5px] text-muted-foreground">
Download als CSV (DE-Excel, semikolongetrennt) oder als xlsx im VDA-ISA-ABGABE-Layout.
</p>
</main>
);
}