Web: alle Kategorie-Auswahlen als aufklappbarer Baum

Statt der nativen "— —"-Dropdowns nutzen jetzt auch Produktformular
(Kategorie + Inline-"Neue Kategorie"-Elternwahl), Einlagern-Maske und
Produkte-Filter den CategorySelect-Baum. CategorySelect um optionale
Zusatz-Eintraege (extraOptions) erweitert, damit der Produkte-Filter
"Alle Kategorien"/"Ohne Kategorie" weiter anbietet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-26 13:07:36 +02:00
parent 956a472c04
commit dc66a6d7ff
4 changed files with 51 additions and 35 deletions

View File

@@ -16,6 +16,9 @@ export default function CategorySelect({
onChange,
nodes,
rootLabel = " oberste Ebene ",
// Zusätzliche Einträge über dem Baum, z.B. { value: 0, label: "Ohne Kategorie" }
// für Filter. Ihr Wert wird unverändert an onChange gereicht.
extraOptions = [],
disabled = false,
}) {
const [open, setOpen] = useState(false);
@@ -29,8 +32,10 @@ export default function CategorySelect({
const idset = new Set(nodes.map((n) => n.id));
const hasKids = (id) => nodes.some((n) => n.parent_id === id);
const selected = value == null || value === "" ? null : Number(value);
const label = selected == null ? rootLabel : nameById[selected] ?? rootLabel;
const isRoot = value == null || value === "";
const extra = extraOptions.find((o) => String(o.value) === String(value));
const selectedId = !isRoot && !extra ? Number(value) : null;
const label = isRoot ? rootLabel : extra ? extra.label : nameById[selectedId] ?? rootLabel;
function place() {
const r = btnRef.current?.getBoundingClientRect();
@@ -101,7 +106,7 @@ export default function CategorySelect({
aria-expanded={open}
onClick={() => setOpen((o) => !o)}
>
<span className={selected == null ? "muted" : ""}>{label}</span>
<span className={isRoot ? "muted" : ""}>{label}</span>
<Icon name="chevronRight" size={13} className={`caret ${open ? "open" : ""}`} />
</button>
@@ -113,12 +118,23 @@ export default function CategorySelect({
>
<button
type="button"
className={`tree-pop-opt ${selected == null ? "sel" : ""}`}
className={`tree-pop-opt ${isRoot ? "sel" : ""}`}
style={{ paddingLeft: 12 }}
onClick={() => pick(null)}
>
{rootLabel}
</button>
{extraOptions.map((o) => (
<button
key={String(o.value)}
type="button"
className={`tree-pop-opt ${extra && String(extra.value) === String(o.value) ? "sel" : ""}`}
style={{ paddingLeft: 12 }}
onClick={() => pick(o.value)}
>
{o.label}
</button>
))}
{visible.map((n) => {
const kids = hasKids(n.id);
const zu = collapsed.has(n.id);
@@ -139,7 +155,7 @@ export default function CategorySelect({
)}
<button
type="button"
className={`tree-pop-opt ${selected === n.id ? "sel" : ""}`}
className={`tree-pop-opt ${selectedId === n.id ? "sel" : ""}`}
onClick={() => pick(n.id)}
>
{n.name}