Web: Verbrauchsgegenstand als dritte Gegenstands-Art + Typ-Position + Thumbnail
ProductForm: Typ/Kategorie an feste Position (nach Marke, vor den typ- spezifischen Feldern) - der Umschalter springt nicht mehr je nach Auswahl. Gegenstaende bekommen eine 3-Wege-Verwaltung: Menge je Lagerort / Einzelstuecke / Verbrauchsgegenstand. Verbrauchsgegenstaende werden wie Lebensmittel als Charge (Einheit, Packung, MHD, Mindestbestand mit Einheit, Gruppe, Chargen-Ansicht) gefuehrt (foodLike = kein Gegenstand ODER bulk). Menge-Gegenstaende koennen jetzt ebenfalls einer Gruppe zugeordnet werden. Thumbnail: .thumb display:block, damit die Zeilenhoehe beim Nachladen nicht mehr minimal springt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
|||||||
const EMPTY = {
|
const EMPTY = {
|
||||||
barcode: "", name: "", brand: "", image_url: "",
|
barcode: "", name: "", brand: "", image_url: "",
|
||||||
unit_id: "", package_size: "", package_label: "", date_precision: "day", min_stock: "",
|
unit_id: "", package_size: "", package_label: "", date_precision: "day", min_stock: "",
|
||||||
group_id: "", category_id: "", shop_id: "", product_url: "", individual: false,
|
group_id: "", category_id: "", shop_id: "", product_url: "", individual: false, bulk: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// (Gebinde kommen jetzt aus der Verwaltung, siehe api.listPackageTypes)
|
// (Gebinde kommen jetzt aus der Verwaltung, siehe api.listPackageTypes)
|
||||||
@@ -108,6 +108,12 @@ export default function ProductForm() {
|
|||||||
// Der Umschalter bestimmt die Verwaltungsart; die Kategorieliste ist darauf
|
// Der Umschalter bestimmt die Verwaltungsart; die Kategorieliste ist darauf
|
||||||
// gefiltert, also passt die gewählte Kategorie immer zum Modus.
|
// gefiltert, also passt die gewählte Kategorie immer zum Modus.
|
||||||
const isObject = modus === "object";
|
const isObject = modus === "object";
|
||||||
|
// Verwaltungsart eines Gegenstands: Menge je Lagerort (count), Einzelstücke
|
||||||
|
// (individual) oder Verbrauchsgegenstand (bulk – wie ein Lebensmittel als Charge).
|
||||||
|
const objMode = form.individual ? "individual" : form.bulk ? "bulk" : "count";
|
||||||
|
// foodLike nutzt die Lebensmittel-Pfade (Chargen, Einheit, MHD, Mindestbestand):
|
||||||
|
// gilt für echte Lebensmittel UND Verbrauchsgegenstände.
|
||||||
|
const foodLike = !isObject || form.bulk;
|
||||||
// Gegenstände gleicht das Backend gegen Open Products Facts ab, Lebensmittel
|
// Gegenstände gleicht das Backend gegen Open Products Facts ab, Lebensmittel
|
||||||
// gegen Open Food Facts – die Beschriftung folgt dem Modus.
|
// gegen Open Food Facts – die Beschriftung folgt dem Modus.
|
||||||
const datenquelle = isObject ? "Open Products Facts" : "Open Food Facts";
|
const datenquelle = isObject ? "Open Products Facts" : "Open Food Facts";
|
||||||
@@ -126,6 +132,11 @@ export default function ProductForm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verwaltungsart eines Gegenstands setzen (individual/bulk schließen sich aus).
|
||||||
|
function setObjMode(mode) {
|
||||||
|
setForm((f) => ({ ...f, individual: mode === "individual", bulk: mode === "bulk" }));
|
||||||
|
}
|
||||||
|
|
||||||
function setField(fieldId, value) {
|
function setField(fieldId, value) {
|
||||||
setFieldValues((v) => ({ ...v, [fieldId]: value }));
|
setFieldValues((v) => ({ ...v, [fieldId]: value }));
|
||||||
}
|
}
|
||||||
@@ -390,6 +401,7 @@ export default function ProductForm() {
|
|||||||
shop_id: p.shop_id ?? "",
|
shop_id: p.shop_id ?? "",
|
||||||
product_url: p.product_url || "",
|
product_url: p.product_url || "",
|
||||||
individual: Boolean(p.individual),
|
individual: Boolean(p.individual),
|
||||||
|
bulk: Boolean(p.bulk),
|
||||||
});
|
});
|
||||||
setFieldValues(p.field_values || {});
|
setFieldValues(p.field_values || {});
|
||||||
setModus(p.tracking === "object" ? "object" : "food");
|
setModus(p.tracking === "object" ? "object" : "food");
|
||||||
@@ -437,35 +449,44 @@ export default function ProductForm() {
|
|||||||
shop_id: form.shop_id === "" ? null : Number(form.shop_id),
|
shop_id: form.shop_id === "" ? null : Number(form.shop_id),
|
||||||
product_url: form.product_url.trim() || null,
|
product_url: form.product_url.trim() || null,
|
||||||
};
|
};
|
||||||
if (isObject) {
|
const minStock = {
|
||||||
// Gegenstände: keine Lebensmittel-Felder (Einheit/Packung/MHD/Gruppe).
|
|
||||||
// Nur die geltenden eigenen Felder mitschicken – Werte fremder Kategorien
|
|
||||||
// (nach einem Kategoriewechsel) fallen dabei weg.
|
|
||||||
const erlaubt = new Set(effFields.map((f) => String(f.id)));
|
|
||||||
const fv = {};
|
|
||||||
Object.entries(fieldValues).forEach(([k, v]) => {
|
|
||||||
if (erlaubt.has(String(k))) fv[k] = v;
|
|
||||||
});
|
|
||||||
// Auch Gegenstände dürfen einen Mindestbestand haben (Verbrauchsmaterial):
|
|
||||||
// gleiche Umrechnung wie bei Lebensmitteln, für Stück-Artikel = Stückzahl.
|
|
||||||
return {
|
|
||||||
...base, individual: form.individual, field_values: fv,
|
|
||||||
min_stock: minBase,
|
min_stock: minBase,
|
||||||
min_stock_unit_id: sel === "package" || sel === "" ? null : Number(sel),
|
min_stock_unit_id: sel === "package" || sel === "" ? null : Number(sel),
|
||||||
min_stock_in_packages: sel === "package",
|
min_stock_in_packages: sel === "package",
|
||||||
};
|
};
|
||||||
}
|
// Gegenstände: nur die geltenden eigenen Felder mitschicken – Werte fremder
|
||||||
|
// Kategorien (nach einem Kategoriewechsel) fallen dabei weg.
|
||||||
|
const erlaubt = new Set(effFields.map((f) => String(f.id)));
|
||||||
|
const fieldPayload = {};
|
||||||
|
Object.entries(fieldValues).forEach(([k, v]) => {
|
||||||
|
if (erlaubt.has(String(k))) fieldPayload[k] = v;
|
||||||
|
});
|
||||||
|
if (foodLike) {
|
||||||
|
// Lebensmittel ODER Verbrauchsgegenstand: als Charge mit Einheit/Packung/
|
||||||
|
// MHD/Mindestbestand/Gruppe geführt. bulk nur bei Gegenständen.
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
individual: false,
|
individual: false,
|
||||||
|
bulk: isObject,
|
||||||
unit_id: form.unit_id === "" ? null : Number(form.unit_id),
|
unit_id: form.unit_id === "" ? null : Number(form.unit_id),
|
||||||
package_size: form.package_size === "" ? null : Number(form.package_size),
|
package_size: form.package_size === "" ? null : Number(form.package_size),
|
||||||
package_label: form.package_label.trim() || null,
|
package_label: form.package_label.trim() || null,
|
||||||
date_precision: form.date_precision || "day",
|
date_precision: form.date_precision || "day",
|
||||||
min_stock: minBase,
|
|
||||||
min_stock_unit_id: sel === "package" || sel === "" ? null : Number(sel),
|
|
||||||
min_stock_in_packages: sel === "package",
|
|
||||||
group_id: form.group_id === "" ? null : Number(form.group_id),
|
group_id: form.group_id === "" ? null : Number(form.group_id),
|
||||||
|
...minStock,
|
||||||
|
...(isObject ? { field_values: fieldPayload } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Gegenstand ohne Charge: Menge je Lagerort oder Einzelstücke. Mindestbestand
|
||||||
|
// in Stück (kein Einheit-Dropdown), gleiche Umrechnung liefert die Stückzahl.
|
||||||
|
// Menge-Gegenstände dürfen (wie Verbrauchsgegenstände) in eine Gruppe.
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
individual: form.individual,
|
||||||
|
bulk: false,
|
||||||
|
group_id: form.group_id === "" ? null : Number(form.group_id),
|
||||||
|
field_values: fieldPayload,
|
||||||
|
...minStock,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +625,100 @@ export default function ProductForm() {
|
|||||||
onSchliessen={() => setOffDaten(null)}
|
onSchliessen={() => setOffDaten(null)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isObject && (<>
|
{/* Typ und Kategorie an fester Position – VOR den typ-spezifischen
|
||||||
|
Feldern, damit der Umschalter nicht je nach Auswahl springt. */}
|
||||||
|
<div className="row">
|
||||||
|
<label className="seg-label">
|
||||||
|
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>
|
||||||
|
<div className="row">
|
||||||
|
<label className="grow" style={{ maxWidth: 460 }}>
|
||||||
|
<span className="tip" title="Nur Kategorien des gewählten Typs. Gegenstands-Kategorien zeigen Menge je Lagerort, Lebensmittel MHD/Chargen.">
|
||||||
|
Kategorie
|
||||||
|
</span>
|
||||||
|
<CategorySelect
|
||||||
|
value={form.category_id === "" ? null : Number(form.category_id)}
|
||||||
|
nodes={asTree(categories).filter((c) => c.tracking === modus)}
|
||||||
|
rootLabel="– keine –"
|
||||||
|
disabled={readOnly}
|
||||||
|
onChange={(id) => set("category_id", id == null ? "" : String(id))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{isAdmin && (
|
||||||
|
<div style={{ display: "flex", alignItems: "flex-end" }}>
|
||||||
|
<button type="button" className="btn" style={{ marginBottom: 2 }}
|
||||||
|
onClick={() => (newCat ? setNewCat(null) : openNewCat())}>
|
||||||
|
<Icon name="plus" size={16} />Neue Kategorie
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isAdmin && newCat && (
|
||||||
|
<div className="card-sub" style={{ marginBottom: "var(--sp-3)" }}>
|
||||||
|
<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>
|
||||||
|
<div className="row">
|
||||||
|
<label className="grow">Name
|
||||||
|
<input value={newCat.name} placeholder="z.B. Powerbank"
|
||||||
|
onChange={(e) => setNewCat({ ...newCat, name: e.target.value })} />
|
||||||
|
</label>
|
||||||
|
<label className="grow">Untergeordnet (optional)
|
||||||
|
<CategorySelect
|
||||||
|
value={newCat.parent_id === "" ? null : Number(newCat.parent_id)}
|
||||||
|
nodes={asTree(categories).filter((c) => c.tracking === modus)}
|
||||||
|
rootLabel="– oberste Ebene –"
|
||||||
|
onChange={(id) => setNewCat({ ...newCat, parent_id: id == null ? "" : String(id) })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="button" className="btn primary" onClick={createCategoryInline}>
|
||||||
|
Anlegen & auswählen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Verwaltungsart nur für Gegenstände: Menge je Lagerort / Einzelstücke /
|
||||||
|
Verbrauchsgegenstand (= wie ein Lebensmittel als Charge geführt). */}
|
||||||
|
{isObject && (
|
||||||
|
<div className="row">
|
||||||
|
<label className="grow seg-label">
|
||||||
|
Verwaltung
|
||||||
|
<div className="segmented seg-wrap">
|
||||||
|
<button type="button" className={objMode === "count" ? "active" : ""}
|
||||||
|
onClick={() => setObjMode("count")} disabled={readOnly}>Menge je Lagerort</button>
|
||||||
|
<button type="button" className={objMode === "individual" ? "active" : ""}
|
||||||
|
onClick={() => setObjMode("individual")} disabled={readOnly}>Einzelstücke</button>
|
||||||
|
<button type="button" className={objMode === "bulk" ? "active" : ""}
|
||||||
|
onClick={() => setObjMode("bulk")} disabled={readOnly}>Verbrauchsgegenstand</button>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isObject && objMode === "bulk" && (
|
||||||
|
<p className="muted small mt-0" style={{ marginBottom: "var(--sp-2)" }}>
|
||||||
|
Wird wie ein Lebensmittel als Charge mit Menge und Einheit geführt
|
||||||
|
(z.B. Sonnencreme in ml) – nur ohne eindeutigen Code.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Lebensmittel ODER Verbrauchsgegenstand: Einheit, Packung, MHD,
|
||||||
|
Mindestbestand (mit Einheit), Gruppe, Mindestbestand je Lagerort. */}
|
||||||
|
{foodLike && (<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<label className="grow">
|
<label className="grow">
|
||||||
Einheit
|
Einheit
|
||||||
@@ -709,86 +823,13 @@ export default function ProductForm() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>)}
|
</>)}
|
||||||
<div className="row">
|
|
||||||
<label className="seg-label">
|
|
||||||
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>
|
|
||||||
<div className="row">
|
|
||||||
<label className="grow" style={{ maxWidth: 460 }}>
|
|
||||||
<span className="tip" title="Nur Kategorien des gewählten Typs. Gegenstands-Kategorien zeigen Menge je Lagerort, Lebensmittel MHD/Chargen.">
|
|
||||||
Kategorie
|
|
||||||
</span>
|
|
||||||
<CategorySelect
|
|
||||||
value={form.category_id === "" ? null : Number(form.category_id)}
|
|
||||||
nodes={asTree(categories).filter((c) => c.tracking === modus)}
|
|
||||||
rootLabel="– keine –"
|
|
||||||
disabled={readOnly}
|
|
||||||
onChange={(id) => set("category_id", id == null ? "" : String(id))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{isAdmin && (
|
|
||||||
<div style={{ display: "flex", alignItems: "flex-end" }}>
|
|
||||||
<button type="button" className="btn" style={{ marginBottom: 2 }}
|
|
||||||
onClick={() => (newCat ? setNewCat(null) : openNewCat())}>
|
|
||||||
<Icon name="plus" size={16} />Neue Kategorie
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isAdmin && newCat && (
|
|
||||||
<div className="card-sub" style={{ marginBottom: "var(--sp-3)" }}>
|
|
||||||
<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>
|
|
||||||
<div className="row">
|
|
||||||
<label className="grow">Name
|
|
||||||
<input value={newCat.name} placeholder="z.B. Powerbank"
|
|
||||||
onChange={(e) => setNewCat({ ...newCat, name: e.target.value })} />
|
|
||||||
</label>
|
|
||||||
<label className="grow">Untergeordnet (optional)
|
|
||||||
<CategorySelect
|
|
||||||
value={newCat.parent_id === "" ? null : Number(newCat.parent_id)}
|
|
||||||
nodes={asTree(categories).filter((c) => c.tracking === modus)}
|
|
||||||
rootLabel="– oberste Ebene –"
|
|
||||||
onChange={(id) => setNewCat({ ...newCat, parent_id: id == null ? "" : String(id) })}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button type="button" className="btn primary" onClick={createCategoryInline}>
|
|
||||||
Anlegen & auswählen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
{/* Gegenstand-spezifische Zusätze: Bezugsquelle/Link und die
|
||||||
|
Stück-Mindestbestände (nur Menge je Lagerort / Einzelstücke). */}
|
||||||
{isObject && (<>
|
{isObject && (<>
|
||||||
|
{objMode !== "bulk" && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<label className="grow seg-label">
|
{objMode === "count" && (
|
||||||
Verwaltung
|
|
||||||
<div className="segmented">
|
|
||||||
<button type="button" className={!form.individual ? "active" : ""}
|
|
||||||
onClick={() => set("individual", false)} disabled={readOnly}>Menge je Lagerort</button>
|
|
||||||
<button type="button" className={form.individual ? "active" : ""}
|
|
||||||
onClick={() => set("individual", true)} disabled={readOnly}>Einzelstücke</button>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div className="grow" />
|
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
{!form.individual && (
|
|
||||||
<label className="grow">
|
<label className="grow">
|
||||||
Gekauft bei
|
Gekauft bei
|
||||||
<select value={form.shop_id} onChange={(e) => set("shop_id", e.target.value)}
|
<select value={form.shop_id} onChange={(e) => set("shop_id", e.target.value)}
|
||||||
@@ -810,6 +851,8 @@ export default function ProductForm() {
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{objMode !== "bulk" && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<label className="grow" style={{ maxWidth: 300 }}>
|
<label className="grow" style={{ maxWidth: 300 }}>
|
||||||
Mindestbestand (Stück)
|
Mindestbestand (Stück)
|
||||||
@@ -817,8 +860,21 @@ export default function ProductForm() {
|
|||||||
onChange={(e) => set("min_stock", e.target.value)} disabled={readOnly}
|
onChange={(e) => set("min_stock", e.target.value)} disabled={readOnly}
|
||||||
placeholder="z.B. 3" />
|
placeholder="z.B. 3" />
|
||||||
</label>
|
</label>
|
||||||
|
{objMode === "count" && (
|
||||||
|
<label className="grow" style={{ maxWidth: 300 }}>
|
||||||
|
<span className="tip" title="Zählt Bestände mehrerer Gegenstände zusammen (in Stück).">
|
||||||
|
Gruppe
|
||||||
|
</span>
|
||||||
|
<select value={form.group_id} onChange={(e) => set("group_id", e.target.value)}
|
||||||
|
disabled={readOnly}>
|
||||||
|
<option value="">– keine –</option>
|
||||||
|
{groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isNew && product && isAdmin && (
|
)}
|
||||||
|
{objMode !== "bulk" && !isNew && product && isAdmin && (
|
||||||
<div style={{ marginTop: "var(--sp-2)", marginBottom: "var(--sp-4)" }}>
|
<div style={{ marginTop: "var(--sp-2)", marginBottom: "var(--sp-4)" }}>
|
||||||
<div className="card-head" style={{ marginBottom: "var(--sp-1)" }}>
|
<div className="card-head" style={{ marginBottom: "var(--sp-1)" }}>
|
||||||
<Icon name="location" size={16} />
|
<Icon name="location" size={16} />
|
||||||
@@ -911,7 +967,22 @@ export default function ProductForm() {
|
|||||||
{readOnly && <p className="muted small mt-0">Nur Administratoren können Produkte bearbeiten.</p>}
|
{readOnly && <p className="muted small mt-0">Nur Administratoren können Produkte bearbeiten.</p>}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{!isNew && (isObject ? (
|
{!isNew && (foodLike ? (
|
||||||
|
// Lebensmittel und Verbrauchsgegenstände: Chargen (Menge, MHD, FEFO).
|
||||||
|
<LotsCard
|
||||||
|
product={product}
|
||||||
|
lots={lots}
|
||||||
|
baseShort={baseShort}
|
||||||
|
warnDays={warnDays}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
onChanged={async () => {
|
||||||
|
setLots(await api.listLots(id));
|
||||||
|
setProduct(await api.getProduct(id));
|
||||||
|
}}
|
||||||
|
onError={setError}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
// Gegenstand als Menge je Lagerort (Einzelstücke stehen unten).
|
||||||
product && !form.individual && (
|
product && !form.individual && (
|
||||||
<ObjektBestand
|
<ObjektBestand
|
||||||
product={product}
|
product={product}
|
||||||
@@ -925,19 +996,6 @@ export default function ProductForm() {
|
|||||||
onError={setError}
|
onError={setError}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : (
|
|
||||||
<LotsCard
|
|
||||||
product={product}
|
|
||||||
lots={lots}
|
|
||||||
baseShort={baseShort}
|
|
||||||
warnDays={warnDays}
|
|
||||||
isAdmin={isAdmin}
|
|
||||||
onChanged={async () => {
|
|
||||||
setLots(await api.listLots(id));
|
|
||||||
setProduct(await api.getProduct(id));
|
|
||||||
}}
|
|
||||||
onError={setError}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -397,6 +397,7 @@ td select { width: auto; min-width: 0; max-width: 100%; }
|
|||||||
.strong { font-weight: 640; }
|
.strong { font-weight: 640; }
|
||||||
.thumb-cell { width: 44px; }
|
.thumb-cell { width: 44px; }
|
||||||
.thumb {
|
.thumb {
|
||||||
|
display: block; /* sonst hebt die Inline-Grundlinie die Zeile beim Nachladen minimal */
|
||||||
width: 34px; height: 34px; object-fit: cover;
|
width: 34px; height: 34px; object-fit: cover;
|
||||||
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
||||||
}
|
}
|
||||||
@@ -1007,6 +1008,8 @@ tr.row-active { background: var(--surface-2); }
|
|||||||
direkt daneben geklebt. */
|
direkt daneben geklebt. */
|
||||||
.seg-label { display: flex; align-items: center; gap: var(--sp-3); }
|
.seg-label { display: flex; align-items: center; gap: var(--sp-3); }
|
||||||
.seg-label .segmented { margin-top: 0; }
|
.seg-label .segmented { margin-top: 0; }
|
||||||
|
/* Dreifach-Umschalter (Verwaltung) darf bei Platznot umbrechen. */
|
||||||
|
.segmented.seg-wrap { flex-wrap: wrap; }
|
||||||
|
|
||||||
/* Artikelfoto mit Aufnahme-/Galerie-Knöpfen */
|
/* Artikelfoto mit Aufnahme-/Galerie-Knöpfen */
|
||||||
.produkt-foto { display: flex; flex-direction: column; gap: var(--sp-2); align-items: center; }
|
.produkt-foto { display: flex; flex-direction: column; gap: var(--sp-2); align-items: center; }
|
||||||
|
|||||||
Reference in New Issue
Block a user