diff --git a/web/src/components/CategorySelect.jsx b/web/src/components/CategorySelect.jsx index de8aff7..5910c9b 100644 --- a/web/src/components/CategorySelect.jsx +++ b/web/src/components/CategorySelect.jsx @@ -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)} > - {label} + {label} @@ -113,12 +118,23 @@ export default function CategorySelect({ > pick(null)} > {rootLabel} + {extraOptions.map((o) => ( + pick(o.value)} + > + {o.label} + + ))} {visible.map((n) => { const kids = hasKids(n.id); const zu = collapsed.has(n.id); @@ -139,7 +155,7 @@ export default function CategorySelect({ )} pick(n.id)} > {n.name} diff --git a/web/src/pages/CheckIn.jsx b/web/src/pages/CheckIn.jsx index 61ad06d..ee96b28 100644 --- a/web/src/pages/CheckIn.jsx +++ b/web/src/pages/CheckIn.jsx @@ -7,6 +7,7 @@ import { ProduktThumb } from "../components/ProduktBild"; import { useToast } from "../toast"; import { suggestionToProduct } from "../offUtils"; import { asTree } from "../categoryTree"; +import CategorySelect from "../components/CategorySelect"; import { buildUnitOptions, fmt, fromMonthInput, isExpired, monthInputToLastDay } from "../units"; const emptyLine = () => ({ quantity: "", best_before: "" }); @@ -138,12 +139,12 @@ export default function CheckIn() { Kategorie - setNewCategoryId(e.target.value)}> - – keine – - {asTree(categories).map((c) => ( - {"— ".repeat(c.depth)}{c.name} - ))} - + setNewCategoryId(id == null ? "" : String(id))} + /> Nur für den Überblick in der Artikelliste. diff --git a/web/src/pages/ProductForm.jsx b/web/src/pages/ProductForm.jsx index 8969d15..e5916c2 100644 --- a/web/src/pages/ProductForm.jsx +++ b/web/src/pages/ProductForm.jsx @@ -10,6 +10,7 @@ import ProduktBild from "../components/ProduktBild"; import { useToast } from "../toast"; import { useSettings } from "../settings"; import { asTree } from "../categoryTree"; +import CategorySelect from "../components/CategorySelect"; import { DynamicFields, FIELD_TYPES } from "../fields"; import ObjektBestand from "../components/ObjektBestand"; import Einzelstuecke from "../components/Einzelstuecke"; @@ -696,15 +697,13 @@ export default function ProductForm() { Kategorie - set("category_id", e.target.value)} - disabled={readOnly}> - – keine – - {asTree(categories).filter((c) => c.tracking === modus).map((c) => ( - - {"— ".repeat(c.depth)}{c.name} - - ))} - + c.tracking === modus)} + rootLabel="– keine –" + disabled={readOnly} + onChange={(id) => set("category_id", id == null ? "" : String(id))} + /> {isAdmin && ( @@ -729,13 +728,12 @@ export default function ProductForm() { onChange={(e) => setNewCat({ ...newCat, name: e.target.value })} /> Untergeordnet (optional) - setNewCat({ ...newCat, parent_id: e.target.value })}> - – oberste Ebene – - {asTree(categories).filter((c) => c.tracking === modus).map((c) => ( - {"— ".repeat(c.depth)}{c.name} - ))} - + c.tracking === modus)} + rootLabel="– oberste Ebene –" + onChange={(id) => setNewCat({ ...newCat, parent_id: id == null ? "" : String(id) })} + /> diff --git a/web/src/pages/Products.jsx b/web/src/pages/Products.jsx index da6c19d..9b1266a 100644 --- a/web/src/pages/Products.jsx +++ b/web/src/pages/Products.jsx @@ -6,6 +6,7 @@ import Icon from "../components/Icon"; import { ProduktThumb } from "../components/ProduktBild"; import { fmt, gebinde, unitShort } from "../units"; import { asTree } from "../categoryTree"; +import CategorySelect from "../components/CategorySelect"; // Wie viele Basiseinheiten eine "Artikeleinheit" umfasst (Gebinde oder Produkteinheit). function unitCount(p) { @@ -83,14 +84,14 @@ export default function Products() { setQ(e.target.value)} /> - - { setCategoryId(e.target.value); load(e.target.value); }}> - Alle Kategorien - Ohne Kategorie - {asTree(categories).filter((c) => !typ || c.tracking === typ).map((c) => ( - {"— ".repeat(c.depth)}{c.name} - ))} - + + !typ || c.tracking === typ)} + rootLabel="Alle Kategorien" + extraOptions={[{ value: 0, label: "Ohne Kategorie" }]} + onChange={(id) => { const v = id == null ? "" : String(id); setCategoryId(v); load(v); }} + /> Suchen