Gruppen hierarchisch: in allen Auswahlfeldern und in der Artikelliste
Die Gruppen-Dropdowns waren flache, alphabetische Listen - man sah nicht, dass "Grillwurst" unter "Wurst" haengt. Ein gemeinsamer Helfer (gruppenOptionen) rollt den Graphen zum Baum aus und rueckt ein; eine Gruppe mit mehreren Obergruppen erscheint unter jeder, denn genau das ist ihr Sinn, und ausgewaehlt wird ohnehin dieselbe ID. Eingerueckt wird mit GESCHUETZTEN Leerzeichen - normale fasst der Browser in einem <option> zusammen, die Einrueckung waere sonst wirkungslos. Betroffen: Gruppen-Auswahl im Artikelformular (beide Zweige), beim Einlagern, die Ziel-Auswahl der Mindestbestaende und die Obergruppen-Mehrfachauswahl (die beim Suchen bewusst abflacht, weil eine Einrueckung ohne die Eltern in die Irre fuehrt). In der App dasselbe in beiden Gruppen-Pickern. Die Gruppen-Spalte der Artikelliste zeigt jetzt den Weg von oben statt eines Etiketts - "Wurst → Grillwurst", mit gedaempften Vorfahren wie bei der Kategorie. Gefiltert wird ueber ALLE Wege: wer auf "Wurst" filtert, sieht auch die Artikel aus den Untergruppen. Dafuer gibt es gruppenInfoMap als Gegenstueck zu categoryInfoMap, das mit mehreren Wegen je Gruppe umgehen kann. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import DataTable from "../components/DataTable";
|
||||
import { ProduktThumb } from "../components/ProduktBild";
|
||||
import CategoryPathLabel, { pathParts } from "../components/CategoryPathLabel";
|
||||
import { categoryInfoMap } from "../categoryPath";
|
||||
import { gruppenInfoMap } from "../groupGraph";
|
||||
import { fmt, gebinde, unitShort } from "../units";
|
||||
|
||||
// Wie viele Basiseinheiten eine "Artikeleinheit" umfasst (Gebinde oder Produkteinheit).
|
||||
@@ -43,6 +44,8 @@ export default function Products({ fixedType = null }) {
|
||||
const { isAdmin } = useAuth();
|
||||
const [products, setProducts] = useState([]);
|
||||
const [categories, setCategories] = useState([]);
|
||||
// Gruppen nur fuer die Pfad-Anzeige der Gruppen-Spalte.
|
||||
const [groups, setGroups] = useState([]);
|
||||
// "" = alle, sonst "food"/"object": trennt Lebensmittel und Gegenstände.
|
||||
const [typ, setTyp] = useState("");
|
||||
const [error, setError] = useState(null);
|
||||
@@ -56,11 +59,13 @@ export default function Products({ fixedType = null }) {
|
||||
.catch((err) => setError(err.message))
|
||||
.finally(() => setLoading(false));
|
||||
api.listCategories().then(setCategories).catch(() => {});
|
||||
api.listGroups().then(setGroups).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Kategorie als Pfad + Tokens (jede Ebene), damit "Klamotten" auch die
|
||||
// Unterkategorien findet und die Oberkategorie im Filter wählbar ist.
|
||||
const catInfo = useMemo(() => categoryInfoMap(categories), [categories]);
|
||||
const grpInfo = useMemo(() => gruppenInfoMap(groups), [groups]);
|
||||
// Zwei getrennte Seiten (Lebensmittel/Gegenstände) fixieren den Typ; sonst der
|
||||
// Umschalter oben.
|
||||
const activeTyp = fixedType || typ;
|
||||
@@ -91,15 +96,20 @@ export default function Products({ fixedType = null }) {
|
||||
filterOptionLabel: (v) => (v === "" ? "(Leere)" : <CategoryPathLabel parts={pathParts(v)} />),
|
||||
sortValue: (p) => catInfo.get(p.category_id)?.path || p.category_name || "",
|
||||
render: (p) => <CategoryPathLabel parts={catInfo.get(p.category_id)?.parts} fallback={p.category_name || "–"} /> },
|
||||
// Gruppe: zählt Bestände mehrerer Marken zusammen. Als eigene Spalte, damit
|
||||
// sich die Liste danach filtern lässt („zeig mir alles aus Pesto Rosso").
|
||||
{ key: "gruppe", header: "Gruppe", width: 170,
|
||||
filterText: (p) => p.group_name || "",
|
||||
filterValues: (p) => [p.group_name || ""],
|
||||
filterOptionLabel: (v) => (v === "" ? "(Ohne Gruppe)" : v),
|
||||
sortValue: (p) => p.group_name || "",
|
||||
render: (p) => (p.group_name
|
||||
? <span className="badge accent">{p.group_name}</span>
|
||||
// Gruppe: zählt Bestände mehrerer Marken zusammen. Mit ihrem Weg von oben
|
||||
// dargestellt, wie die Kategorie – ein Etikett verschwiege, dass „Grillwurst"
|
||||
// unter „Wurst" hängt. Gefiltert wird über ALLE Wege, damit „Wurst" auch die
|
||||
// Artikel aus den Untergruppen zeigt.
|
||||
{ key: "gruppe", header: "Gruppe", width: 200,
|
||||
filterText: (p) => grpInfo.get(p.group_id)?.path || p.group_name || "",
|
||||
filterValues: (p) => grpInfo.get(p.group_id)?.tokens
|
||||
|| (p.group_name ? [p.group_name] : [""]),
|
||||
filterOptionLabel: (v) => (v === "" ? "(Ohne Gruppe)" : <CategoryPathLabel parts={pathParts(v)} />),
|
||||
sortValue: (p) => grpInfo.get(p.group_id)?.path || p.group_name || "",
|
||||
render: (p) => (p.group_id != null
|
||||
? <span title={(grpInfo.get(p.group_id)?.alle || []).join(" · ")}>
|
||||
<CategoryPathLabel parts={grpInfo.get(p.group_id)?.parts} fallback={p.group_name || "–"} />
|
||||
</span>
|
||||
: <span className="muted">–</span>) },
|
||||
// Nur in der Gegenstände-Liste: Menge je Lagerort / Einzelstücke / Verbrauchsgegenstand.
|
||||
activeTyp === "object" && { key: "verwaltung", header: "Verwaltung", label: "Verwaltung", width: 190,
|
||||
|
||||
Reference in New Issue
Block a user