Files
Laufzettel/src/components/vorlagen/editor-abschnitt.tsx
T
florian.wilde 855164066c
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(vorlagen): Abschnitte nur fuer bestimmte Abteilungen (#58)
2026-10-04 18:04:51 +00:00

203 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { Blocks } from "lucide-react";
import { AbteilungenWaehlen } from "@/components/vorlagen/abteilungen-waehlen";
import { KlappKarte, LoeschKnopf, TextKnopf, Textfeld } from "@/components/vorlagen/editor-felder";
import { SortierBereich, SortierZeile, VerschiebeKnoepfe } from "@/components/vorlagen/editor-sortieren";
import { SchrittKarte } from "@/components/vorlagen/editor-schritt";
import { abschnittNummer, type EditorAbschnitt, type EditorSchritt } from "@/lib/editor-aufbau";
import { GRENZEN } from "@/lib/vorlagen-format";
/**
* Ein Abschnitt mit seinen Schritten (#010).
*
* Die Nummer links steht fuer die Position und wird nie gespeichert — wer den
* Abschnitt verschiebt, bekommt sofort die neue Nummer zu sehen.
*
* Aufgeklappt als Standard, anders als Schritte und Variablen: man arbeitet in
* aller Regel in einem Abschnitt und will seine Schritte sehen. Zugeklappt steht
* die Zahl der Schritte daneben, damit beim Umsortieren klar ist, was da
* gerade wandert.
*
* Schritte werden innerhalb ihres Abschnitts sortiert; jeder Abschnitt hat dazu
* seinen eigenen Sortierbereich. Ein Schritt laesst sich damit nicht in einen
* anderen Abschnitt ziehen — das braucht die Vorlagenpflege nicht, und ein
* Bereich ueber alle Abschnitte hinweg haette am Handy Ziehwege von mehreren
* Bildschirmhoehen.
*/
export function AbschnittKarte({
abschnitt,
index,
anzahl,
lesend,
abteilungen,
zuletztAngelegt,
anfasser,
aendern,
loeschen,
aufSortiert,
schrittAendern,
schrittHinzufuegen,
schrittLoeschen,
schritteUmsortieren,
}: {
abschnitt: EditorAbschnitt;
index: number;
anzahl: number;
lesend: boolean;
/** Die gepflegten Abteilungsnamen – für die Einschränkung (#031). */
abteilungen: readonly string[];
/** Kennung der zuletzt angelegten Zeile — die wird aufgeklappt gezeigt. */
zuletztAngelegt: string | null;
anfasser: React.ReactNode;
aendern: (teil: Partial<EditorAbschnitt>) => void;
loeschen: () => void;
aufSortiert: (von: number, nach: number) => void;
schrittAendern: (kennung: string, teil: Partial<EditorSchritt>) => void;
schrittHinzufuegen: () => void;
schrittLoeschen: (kennung: string) => void;
schritteUmsortieren: (von: number, nach: number) => void;
}) {
const [offen, setzeOffen] = useState(true);
const voll = abschnitt.schritte.length >= GRENZEN.schritteJeAbschnitt;
const anzahlSchritte = abschnitt.schritte.length;
const baustein = abschnitt.baustein;
return (
<KlappKarte
offen={offen}
umschalten={() => setzeOffen((vorher) => !vorher)}
flaeche="flaeche"
ueberschrift
anfasser={anfasser}
kopf={
<>
<span className="zahl shrink-0 text-lg text-akzent">{abschnittNummer(index)}</span>
<span className="shrink-0 text-nebentext">·</span>
<span className={`truncate font-semibold ${abschnitt.titel.trim() === "" ? "text-nebentext" : ""}`}>
{abschnitt.titel.trim() === "" ? "ohne Titel" : abschnitt.titel}
</span>
<span className="shrink-0 text-xs font-normal text-nebentext">
{anzahlSchritte} {anzahlSchritte === 1 ? "Schritt" : "Schritte"}
</span>
{baustein ? (
<span className="inline-flex shrink-0 items-center gap-1 rounded-pille bg-flaeche-hoch px-2.5 py-0.5 text-xs font-normal text-nebentext">
<Blocks className="size-3.5" strokeWidth={1.75} aria-hidden="true" />
Baustein
</span>
) : null}
{abschnitt.nurFuerAbteilungen.length > 0 ? (
<span className="shrink-0 rounded-pille bg-flaeche-hoch px-2.5 py-0.5 text-xs font-normal text-nebentext">
nur {abschnitt.nurFuerAbteilungen.join(", ")}
</span>
) : null}
</>
}
knoepfe={
lesend ? null : (
<>
<VerschiebeKnoepfe art="Abschnitt" index={index} anzahl={anzahl} aufSortiert={aufSortiert} />
<LoeschKnopf
beschriftung="Abschnitt löschen"
{...(anzahlSchritte > 0
? {
frage: `Abschnitt mit ${anzahlSchritte} ${
anzahlSchritte === 1 ? "Schritt" : "Schritten"
} löschen?`,
}
: {})}
aufLoeschen={loeschen}
/>
</>
)
}
>
{/*
Ein Baustein-Abschnitt ist eine **Einheit** (#030): weder Titel noch
Schritte lassen sich hier aendern. Beides kommt beim Veroeffentlichen aus
dem Baustein, und ein Feld, das man ausfuellen kann und das danach
ueberschrieben wird, waere eine Falle. Geaendert wird am Baustein.
*/}
{baustein ? (
<>
<p className="max-w-prose text-sm text-nebentext">
Dieser Abschnitt kommt aus dem Baustein{" "}
<span className="font-semibold text-text">{baustein.name}</span>
{baustein.version > 0 ? `, Fassung ${baustein.version}` : ""}. Titel und Schritte werden beim
Veröffentlichen von dort eingesetzt — geändert wird sie am Baustein, nicht hier.
</p>
<p className="max-w-prose text-sm text-nebentext">
Eine neue Fassung des Bausteins wirkt erst in der <strong>nächsten</strong> veröffentlichten
Version dieser Vorlage. Schon veröffentlichte Versionen und laufende Arbeiten bleiben, wie sie
sind.
</p>
<p className="text-sm text-nebentext">
{anzahlSchritte} {anzahlSchritte === 1 ? "Schritt" : "Schritte"} aus dem Baustein.
</p>
</>
) : null}
{!baustein && !lesend ? (
<Textfeld
beschriftung="Titel des Abschnitts"
wert={abschnitt.titel}
setzen={(titel) => aendern({ titel })}
lesend={lesend}
vorschlag="Vorbereitung"
/>
) : null}
{/*
Die Einschraenkung gehoert zur Vorlage und nicht zum Baustein — deshalb
steht sie auch bei einem Baustein-Abschnitt (#031).
*/}
<AbteilungenWaehlen
gewaehlt={abschnitt.nurFuerAbteilungen}
bekannte={abteilungen}
lesend={lesend}
setzen={(nurFuerAbteilungen) => aendern({ nurFuerAbteilungen })}
/>
{baustein ? null : anzahlSchritte === 0 ? (
<p className="text-sm text-nebentext">Noch kein Schritt in diesem Abschnitt.</p>
) : (
<SortierBereich
kennungen={abschnitt.schritte.map((schritt) => schritt.kennung)}
art="Schritt"
lesend={lesend}
aufSortiert={schritteUmsortieren}
>
<ul className="flex flex-col gap-3">
{abschnitt.schritte.map((schritt, stelle) => (
<SortierZeile key={schritt.kennung} kennung={schritt.kennung} lesend={lesend}>
{(schrittAnfasser) => (
<SchrittKarte
schritt={schritt}
index={stelle}
anzahl={anzahlSchritte}
lesend={lesend}
neu={schritt.kennung === zuletztAngelegt}
anfasser={schrittAnfasser}
aendern={(teil) => schrittAendern(schritt.kennung, teil)}
loeschen={() => schrittLoeschen(schritt.kennung)}
aufSortiert={schritteUmsortieren}
/>
)}
</SortierZeile>
))}
</ul>
</SortierBereich>
)}
{baustein || lesend ? null : (
<TextKnopf
beschriftung={voll ? `Höchstens ${GRENZEN.schritteJeAbschnitt} Schritte` : "Schritt hinzufügen"}
aus={voll}
aufKlick={schrittHinzufuegen}
/>
)}
</KlappKarte>
);
}