feat(vorlagen): Editor, Liste und Versionsgeschichte (#24)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

This commit was merged in pull request #24.
This commit is contained in:
2026-09-25 08:23:22 +00:00
parent d8d5444e9f
commit e3d80e5982
65 changed files with 5454 additions and 103 deletions
@@ -0,0 +1,149 @@
"use client";
import { useState } from "react";
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,
zuletztAngelegt,
anfasser,
aendern,
loeschen,
aufSortiert,
schrittAendern,
schrittHinzufuegen,
schrittLoeschen,
schritteUmsortieren,
}: {
abschnitt: EditorAbschnitt;
index: number;
anzahl: number;
lesend: boolean;
/** 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;
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>
</>
}
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}
/>
</>
)
}
>
{lesend ? null : (
<Textfeld
beschriftung="Titel des Abschnitts"
wert={abschnitt.titel}
setzen={(titel) => aendern({ titel })}
lesend={lesend}
vorschlag="Vorbereitung"
/>
)}
{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>
)}
{lesend ? null : (
<TextKnopf
beschriftung={voll ? `Höchstens ${GRENZEN.schritteJeAbschnitt} Schritte` : "Schritt hinzufügen"}
aus={voll}
aufKlick={schrittHinzufuegen}
/>
)}
</KlappKarte>
);
}