203 lines
7.7 KiB
TypeScript
203 lines
7.7 KiB
TypeScript
"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>
|
||
);
|
||
}
|