185 lines
7.3 KiB
TypeScript
185 lines
7.3 KiB
TypeScript
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import { ArrowLeft, Blocks } from "lucide-react";
|
|
import { BausteinEditor } from "@/components/bausteine/baustein-editor";
|
|
import { BausteinKopf } from "@/components/bausteine/baustein-kopf";
|
|
import { VersionStatus } from "@/generated/prisma/enums";
|
|
import { darf } from "@/server/auth/rollen";
|
|
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
|
|
import { bausteinHolen, bausteinVerwendungen } from "@/server/queries/bausteine";
|
|
|
|
/**
|
|
* Ein Baustein (#030).
|
|
*
|
|
* Oben die Kopfdaten, dann der Entwurf, dann die Fassungen und zuletzt die
|
|
* Verwendungen. Die Reihenfolge ist die der Fragen: woran arbeite ich, was gab
|
|
* es vorher, und wen trifft eine Änderung.
|
|
*/
|
|
|
|
const ZEITPUNKT = new Intl.DateTimeFormat("de-DE", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
|
|
export default async function BausteinSeite({ params }: { params: Promise<{ id: string }> }) {
|
|
const { id } = await params;
|
|
const { benutzer } = await sitzungErforderlich("lesen", `/vorlagen/bausteine/${id}`);
|
|
const baustein = await bausteinHolen(id);
|
|
if (!baustein) notFound();
|
|
|
|
const darfBearbeiten = darf(benutzer.rolle, "vorlagenBearbeiten");
|
|
const verwendungen = await bausteinVerwendungen(id);
|
|
const entwurf = baustein.versionen.find((version) => version.status === VersionStatus.ENTWURF);
|
|
const veroeffentlichte = baustein.versionen.filter(
|
|
(version) => version.status === VersionStatus.VEROEFFENTLICHT,
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Link
|
|
href="/vorlagen/bausteine"
|
|
className="inline-flex min-h-11 items-center gap-2 text-sm text-nebentext hover:text-text"
|
|
>
|
|
<ArrowLeft className="size-4" strokeWidth={1.75} aria-hidden="true" />
|
|
Alle Bausteine
|
|
</Link>
|
|
|
|
<h1 className="mt-2 flex items-center gap-2 text-2xl font-bold">
|
|
<Blocks className="size-6 text-nebentext" strokeWidth={1.75} aria-hidden="true" />
|
|
<span className={baustein.archiviert ? "line-through" : ""}>{baustein.name}</span>
|
|
</h1>
|
|
{baustein.archiviert ? (
|
|
<p className="mt-2 max-w-prose text-sm text-faellig">
|
|
Archiviert. Er lässt sich nicht mehr in einen Entwurf einsetzen, bleibt aber in jeder Vorlage
|
|
lesbar, die ihn schon benutzt.
|
|
</p>
|
|
) : null}
|
|
|
|
{darfBearbeiten ? (
|
|
<div className="mt-6">
|
|
<BausteinKopf
|
|
baustein={{
|
|
id: baustein.id,
|
|
name: baustein.name,
|
|
beschreibung: baustein.beschreibung ?? "",
|
|
archiviert: baustein.archiviert,
|
|
}}
|
|
/>
|
|
</div>
|
|
) : baustein.beschreibung ? (
|
|
<p className="mt-4 max-w-prose text-nebentext">{baustein.beschreibung}</p>
|
|
) : null}
|
|
|
|
{darfBearbeiten && !baustein.archiviert ? (
|
|
<section className="mt-8">
|
|
<h2 className="sr-only">Entwurf</h2>
|
|
{entwurf ? (
|
|
<BausteinEditor
|
|
entwurf={{
|
|
bausteinId: baustein.id,
|
|
versionId: entwurf.id,
|
|
version: entwurf.version,
|
|
titel: entwurf.titel,
|
|
schritte: entwurf.schritte.map((schritt) => ({
|
|
titel: schritt.titel,
|
|
hilfetext: schritt.hilfetext,
|
|
typ: schritt.typ,
|
|
pflicht: schritt.pflicht,
|
|
konfig: schritt.konfig,
|
|
dokuUrl: schritt.dokuUrl,
|
|
})),
|
|
darfVerwerfen: veroeffentlichte.length > 0,
|
|
}}
|
|
/>
|
|
) : (
|
|
/* Kein Entwurf: der Knopf erzeugt ihn als tiefe Kopie der letzten
|
|
veröffentlichten Fassung — dieselbe Regel wie bei Vorlagen. */
|
|
<BausteinKopf
|
|
baustein={{
|
|
id: baustein.id,
|
|
name: baustein.name,
|
|
beschreibung: baustein.beschreibung ?? "",
|
|
archiviert: baustein.archiviert,
|
|
}}
|
|
nurEntwurfKnopf
|
|
/>
|
|
)}
|
|
</section>
|
|
) : null}
|
|
|
|
<section className="mt-8 rounded-karte border border-linie bg-flaeche p-5">
|
|
<h2 className="text-lg font-semibold">Fassungen</h2>
|
|
{veroeffentlichte.length === 0 ? (
|
|
<p className="mt-2 text-sm text-nebentext">Noch keine veröffentlichte Fassung.</p>
|
|
) : (
|
|
<ul className="mt-3 flex flex-col gap-2">
|
|
{veroeffentlichte.map((version) => (
|
|
<li
|
|
key={version.id}
|
|
className="flex flex-wrap items-baseline gap-x-3 gap-y-1 border-t border-linie py-2 text-sm first:border-t-0"
|
|
>
|
|
<span className="zahl shrink-0 text-akzent">{String(version.version).padStart(2, "0")}</span>
|
|
<span className="min-w-0 break-words">{version.titel}</span>
|
|
<span className="text-nebentext">
|
|
{version.schritte.length} {version.schritte.length === 1 ? "Schritt" : "Schritte"}
|
|
</span>
|
|
{version.veroeffentlichtAm ? (
|
|
<span className="mono text-xs text-nebentext">
|
|
{ZEITPUNKT.format(version.veroeffentlichtAm)}
|
|
</span>
|
|
) : null}
|
|
{version.aenderungsnotiz ? (
|
|
<span className="min-w-0 break-words text-nebentext">· {version.aenderungsnotiz}</span>
|
|
) : null}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
|
|
<section className="mt-4 rounded-karte border border-linie bg-flaeche p-5">
|
|
<h2 className="text-lg font-semibold">Wo er steckt</h2>
|
|
{verwendungen.length === 0 ? (
|
|
<p className="mt-2 text-sm text-nebentext">
|
|
In keiner Vorlagenversion. Eine Änderung trifft damit nichts Bestehendes.
|
|
</p>
|
|
) : (
|
|
<>
|
|
<p className="mt-1 max-w-prose text-sm text-nebentext">
|
|
Jede Zeile nennt die Fassung, die dort <strong>eingesetzt</strong> ist. Eine neue Fassung ändert
|
|
davon nichts.
|
|
</p>
|
|
<ul className="mt-3 flex flex-col gap-2">
|
|
{verwendungen.map((eintrag, stelle) => (
|
|
<li
|
|
key={`${eintrag.vorlageId}-${eintrag.vorlagenVersion}-${stelle}`}
|
|
className="flex flex-wrap items-baseline gap-x-3 gap-y-1 border-t border-linie py-2 text-sm first:border-t-0"
|
|
>
|
|
<Link
|
|
href={`/vorlagen/${eintrag.vorlageId}`}
|
|
className={`min-w-0 break-words text-akzent underline underline-offset-2 ${
|
|
eintrag.archiviert ? "line-through" : ""
|
|
}`}
|
|
>
|
|
{eintrag.vorlageName}
|
|
</Link>
|
|
<span className="text-nebentext">Version {eintrag.vorlagenVersion}</span>
|
|
<span className="rounded-pille bg-flaeche-hoch px-2.5 py-0.5 text-xs text-nebentext">
|
|
Fassung {eintrag.bausteinVersion}
|
|
</span>
|
|
{eintrag.status === VersionStatus.ENTWURF ? (
|
|
<span className="text-xs text-faellig">Entwurf</span>
|
|
) : null}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</section>
|
|
</>
|
|
);
|
|
}
|