Web-Artikelformular: Umschalter Lebensmittel/Gegenstand
Statt die Verwaltungsart nur implizit ueber die Kategorie zu waehlen (und dabei Werkzeug & Co. zwischen 50 Lebensmittel-Kategorien zu suchen), gibt es jetzt oben einen klaren Umschalter. Die Kategorieauswahl zeigt nur Kategorien des gewaehlten Typs; neu angelegte Kategorien erben den Typ automatisch. Neuanlage startet als Gegenstand, beim Bearbeiten kommt der Modus aus dem Artikel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -79,8 +79,12 @@ export default function ProductForm() {
|
||||
// Gegenstände: effektive (vererbte) Felder der Kategorie + deren Werte am Artikel.
|
||||
const [effFields, setEffFields] = useState([]);
|
||||
const [fieldValues, setFieldValues] = useState({});
|
||||
// Umschalter Lebensmittel/Gegenstand: filtert die Kategorieauswahl auf den Typ.
|
||||
// Neuanlage startet als Gegenstand (der häufigere Non-Food-Fall); beim Bearbeiten
|
||||
// wird er aus dem Artikel gesetzt.
|
||||
const [modus, setModus] = useState("object");
|
||||
// Direkt im Formular neue Kategorien/Felder anlegen.
|
||||
const [newCat, setNewCat] = useState(null); // { name, parent_id, tracking } | null
|
||||
const [newCat, setNewCat] = useState(null); // { name, parent_id } | null
|
||||
const [newField, setNewField] = useState(null); // { label, field_type, unit, options, required } | null
|
||||
const [error, setError] = useState(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -96,11 +100,9 @@ export default function ProductForm() {
|
||||
const unitFactor = selectedUnit ? selectedUnit.factor : 1;
|
||||
const unitName = selectedUnit ? selectedUnit.name : "";
|
||||
|
||||
// Die gewählte Kategorie bestimmt die Verwaltungsart. Ohne Kategorie: Lebensmittel.
|
||||
const currentCategory =
|
||||
categories.find((c) => String(c.id) === String(form.category_id)) || null;
|
||||
const mode = currentCategory ? currentCategory.tracking : "food";
|
||||
const isObject = mode === "object";
|
||||
// Der Umschalter bestimmt die Verwaltungsart; die Kategorieliste ist darauf
|
||||
// gefiltert, also passt die gewählte Kategorie immer zum Modus.
|
||||
const isObject = modus === "object";
|
||||
// Gegenstände gleicht das Backend gegen Open Products Facts ab, Lebensmittel
|
||||
// gegen Open Food Facts – die Beschriftung folgt dem Modus.
|
||||
const datenquelle = isObject ? "Open Products Facts" : "Open Food Facts";
|
||||
@@ -109,6 +111,16 @@ export default function ProductForm() {
|
||||
setForm((f) => ({ ...f, [k]: v }));
|
||||
}
|
||||
|
||||
// Typ umschalten: passt die gewählte Kategorie nicht mehr, wird sie geleert.
|
||||
function changeModus(neu) {
|
||||
setModus(neu);
|
||||
const cat = categories.find((c) => String(c.id) === String(form.category_id));
|
||||
if (cat && cat.tracking !== neu) {
|
||||
set("category_id", "");
|
||||
setEffFields([]);
|
||||
}
|
||||
}
|
||||
|
||||
function setField(fieldId, value) {
|
||||
setFieldValues((v) => ({ ...v, [fieldId]: value }));
|
||||
}
|
||||
@@ -116,11 +128,8 @@ export default function ProductForm() {
|
||||
// Öffnet den Inline-Dialog „Neue Kategorie" – als Unterkategorie der gerade
|
||||
// gewählten (falls eine gewählt ist), im selben Modus.
|
||||
function openNewCat() {
|
||||
setNewCat({
|
||||
name: "",
|
||||
parent_id: form.category_id || "",
|
||||
tracking: currentCategory ? currentCategory.tracking : "object",
|
||||
});
|
||||
// Neue Kategorie erbt den aktuell gewählten Typ (Umschalter).
|
||||
setNewCat({ name: "", parent_id: form.category_id || "", tracking: modus });
|
||||
}
|
||||
|
||||
async function createCategoryInline() {
|
||||
@@ -230,6 +239,10 @@ export default function ProductForm() {
|
||||
group_id: f.group_id || (res.group_id ? String(res.group_id) : ""),
|
||||
}));
|
||||
if (s.package_size != null) setMinUnit("package");
|
||||
if (res.category_id) {
|
||||
const cat = categories.find((c) => String(c.id) === String(res.category_id));
|
||||
if (cat) setModus(cat.tracking);
|
||||
}
|
||||
toast(
|
||||
`Daten von ${datenquelle} übernommen.` +
|
||||
(s.package_size != null ? "" : " (Füllmenge nicht hinterlegt – bitte Packungsgröße prüfen.)") +
|
||||
@@ -373,6 +386,7 @@ export default function ProductForm() {
|
||||
product_url: p.product_url || "",
|
||||
});
|
||||
setFieldValues(p.field_values || {});
|
||||
setModus(p.tracking === "object" ? "object" : "food");
|
||||
setLots(await api.listLots(id));
|
||||
} else {
|
||||
const bc = searchParams.get("barcode");
|
||||
@@ -656,15 +670,31 @@ export default function ProductForm() {
|
||||
</>)}
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
<span className="tip" title="Bestimmt zugleich die Verwaltungsart: Gegenstands-Kategorien blenden MHD/Chargen aus und zeigen Menge je Lagerort.">
|
||||
Typ
|
||||
<div className="segmented">
|
||||
<button type="button" className={modus === "food" ? "active" : ""}
|
||||
onClick={() => changeModus("food")} disabled={readOnly}>
|
||||
Lebensmittel
|
||||
</button>
|
||||
<button type="button" className={modus === "object" ? "active" : ""}
|
||||
onClick={() => changeModus("object")} disabled={readOnly}>
|
||||
Gegenstand
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<div className="grow" />
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
<span className="tip" title="Nur Kategorien des gewählten Typs. Gegenstands-Kategorien zeigen Menge je Lagerort, Lebensmittel MHD/Chargen.">
|
||||
Kategorie
|
||||
</span>
|
||||
<select value={form.category_id} onChange={(e) => set("category_id", e.target.value)}
|
||||
disabled={readOnly}>
|
||||
<option value="">– keine –</option>
|
||||
{asTree(categories).map((c) => (
|
||||
{asTree(categories).filter((c) => c.tracking === modus).map((c) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{"— ".repeat(c.depth)}{c.name}{c.tracking === "object" ? " · Gegenstand" : ""}
|
||||
{"— ".repeat(c.depth)}{c.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -681,7 +711,8 @@ export default function ProductForm() {
|
||||
|
||||
{isAdmin && newCat && (
|
||||
<div className="card-sub" style={{ marginBottom: "var(--sp-3)" }}>
|
||||
<div className="card-head"><Icon name="tag" /><h3>Neue Kategorie / Unterkategorie</h3>
|
||||
<div className="card-head"><Icon name="tag" />
|
||||
<h3>Neue {modus === "object" ? "Gegenstands" : "Lebensmittel"}-Kategorie</h3>
|
||||
<button type="button" className="btn-icon" style={{ marginLeft: "auto" }}
|
||||
onClick={() => setNewCat(null)}><Icon name="close" size={16} /></button>
|
||||
</div>
|
||||
@@ -694,18 +725,11 @@ export default function ProductForm() {
|
||||
<select value={newCat.parent_id}
|
||||
onChange={(e) => setNewCat({ ...newCat, parent_id: e.target.value })}>
|
||||
<option value="">– oberste Ebene –</option>
|
||||
{asTree(categories).map((c) => (
|
||||
{asTree(categories).filter((c) => c.tracking === modus).map((c) => (
|
||||
<option key={c.id} value={c.id}>{"— ".repeat(c.depth)}{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label style={{ width: 150 }}>Art
|
||||
<select value={newCat.tracking}
|
||||
onChange={(e) => setNewCat({ ...newCat, tracking: e.target.value })}>
|
||||
<option value="object">Gegenstand</option>
|
||||
<option value="food">Lebensmittel</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" className="btn primary" onClick={createCategoryInline}>
|
||||
Anlegen & auswählen
|
||||
|
||||
Reference in New Issue
Block a user