Web: zentrale Mindestbestaende per "+ hinzufuegen" anlegen

Neuer Dialog auf der Mindestbestaende-Seite: Ziel (Produkt/Gegenstand oder
Gruppe) -> Geltung (Gesamt oder Lagerort) -> Menge in der Einheit des Ziels.
Speichert ueber die vorhandenen Endpunkte (updateProduct/updateGroup bzw.
set*LocationMinStock).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-27 20:41:57 +02:00
parent 4087277bb0
commit f287f8f768

View File

@@ -17,6 +17,8 @@ const articleLabel = (p) => p.package_label || p.unit_name || "Stück";
* Bedarfe je Lagerort, inline editierbar. Ergänzt die einzelnen Produkt-/Gruppen-
* Formulare um einen Überblick an einem Ort.
*/
const EMPTY_DRAFT = { kind: "product", targetId: "", scope: "global", locId: "", menge: "" };
export default function MinStock() {
const { isAdmin } = useAuth();
const [products, setProducts] = useState([]);
@@ -24,6 +26,9 @@ export default function MinStock() {
const [locations, setLocations] = useState([]);
const [categories, setCategories] = useState([]);
const [error, setError] = useState(null);
// „+ hinzufügen"-Dialog: Ziel (Produkt/Gruppe) → Geltung (Gesamt/Lagerort) → Menge.
const [showAdd, setShowAdd] = useState(false);
const [draft, setDraft] = useState(EMPTY_DRAFT);
async function load() {
try {
@@ -109,6 +114,53 @@ export default function MinStock() {
} catch (err) { setError(err.message); }
}
// Ausgewähltes Ziel des Dialogs + dessen Einheit (Menge wird darin erfasst).
const draftTarget = draft.kind === "product"
? products.find((p) => String(p.id) === String(draft.targetId))
: groups.find((g) => String(g.id) === String(draft.targetId));
const draftUnit = draft.kind === "product"
? (draftTarget ? articleLabel(draftTarget) : "")
: (draftTarget ? (draftTarget.min_stock_unit_name || "Stück") : "");
function resetAdd() {
setShowAdd(false);
setDraft(EMPTY_DRAFT);
}
async function saveNew() {
setError(null);
const roh = String(draft.menge).trim().replace(",", ".");
const num = roh === "" ? null : Number(roh);
if (!draft.targetId) { setError("Bitte ein Ziel wählen."); return; }
if (num == null || Number.isNaN(num) || num <= 0) { setError("Bitte eine Menge größer 0 angeben."); return; }
if (draft.scope === "loc" && !draft.locId) { setError("Bitte einen Lagerort wählen."); return; }
try {
if (draft.kind === "product") {
const p = draftTarget;
if (draft.scope === "global") {
// Menge in der Artikeleinheit erfasst → in Basiseinheiten speichern,
// wie beim Inline-Editieren ohne bestehenden Wert.
await api.updateProduct(p.id, {
min_stock: Math.round(num * articleUnit(p)),
min_stock_in_packages: (p.package_size || 0) > 0,
min_stock_unit_id: null,
});
} else {
await api.setProductLocationMinStock(p.id, mergeLoc(p.location_min_stocks, draft.locId, num));
}
} else {
const g = draftTarget;
if (draft.scope === "global") {
await api.updateGroup(g.id, { min_stock: num });
} else {
await api.setGroupLocationMinStock(g.id, mergeLoc(g.location_min_stocks, draft.locId, num));
}
}
resetAdd();
await load();
} catch (err) { setError(err.message); }
}
const columns = [
{ key: "art", header: "Art", width: 96, filterText: (r) => (r.kind === "group" ? "Gruppe" : "Produkt"),
render: (r) => <span className="badge nowrap">{r.kind === "group" ? "Gruppe" : "Produkt"}</span> },
@@ -146,8 +198,81 @@ export default function MinStock() {
Gesamt- und Lagerort-Bedarfe von Produkten und Gruppen an einem Ort{isAdmin ? " Werte direkt editierbar" : ""}.
</div>
</div>
{isAdmin && (
<button className="btn primary" onClick={() => (showAdd ? resetAdd() : setShowAdd(true))}>
<Icon name={showAdd ? "close" : "plus"} size={16} />
{showAdd ? "Abbrechen" : "Mindestbestand"}
</button>
)}
</div>
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
{isAdmin && showAdd && (
<div className="card" style={{ marginBottom: "var(--sp-3)" }}>
<div className="card-head"><Icon name="plus" /><h2>Mindestbestand hinzufügen</h2></div>
<div className="row">
<label className="seg-label">
Ziel
<div className="segmented">
<button type="button" className={draft.kind === "product" ? "active" : ""}
onClick={() => setDraft({ ...draft, kind: "product", targetId: "" })}>Gegenstand / Lebensmittel</button>
<button type="button" className={draft.kind === "group" ? "active" : ""}
onClick={() => setDraft({ ...draft, kind: "group", targetId: "" })}>Gruppe</button>
</div>
</label>
</div>
<div className="row">
<label className="grow">
{draft.kind === "product" ? "Produkt / Gegenstand" : "Gruppe"}
<select value={draft.targetId} onChange={(e) => setDraft({ ...draft, targetId: e.target.value })}>
<option value=""> wählen </option>
{(draft.kind === "product"
? [...products].sort((a, b) => a.name.localeCompare(b.name, "de"))
: [...groups].sort((a, b) => a.name.localeCompare(b.name, "de"))
).map((t) => (
<option key={t.id} value={t.id}>
{t.name}{draft.kind === "product" && t.brand ? ` · ${t.brand}` : ""}
</option>
))}
</select>
</label>
</div>
<div className="row">
<label className="seg-label">
Geltung
<div className="segmented">
<button type="button" className={draft.scope === "global" ? "active" : ""}
onClick={() => setDraft({ ...draft, scope: "global", locId: "" })}>Gesamt</button>
<button type="button" className={draft.scope === "loc" ? "active" : ""}
onClick={() => setDraft({ ...draft, scope: "loc" })}>Lagerort</button>
</div>
</label>
{draft.scope === "loc" && (
<label className="grow" style={{ maxWidth: 360 }}>
Lagerort
<select value={draft.locId} onChange={(e) => setDraft({ ...draft, locId: e.target.value })}>
<option value=""> wählen </option>
{locations.map((l) => (
<option key={l.id} value={l.id}>{locationPathById(l.id, locations)}</option>
))}
</select>
</label>
)}
</div>
<div className="row">
<label className="grow" style={{ maxWidth: 300 }}>
Menge{draftUnit ? ` (in ${draftUnit})` : ""}
<input type="number" step="any" min="0" value={draft.menge}
onChange={(e) => setDraft({ ...draft, menge: e.target.value })} placeholder="z.B. 2" />
</label>
</div>
<div className="field-inline" style={{ marginTop: "var(--sp-2)" }}>
<button className="btn primary" onClick={saveNew}>Speichern</button>
<button className="btn" onClick={resetAdd}>Abbrechen</button>
</div>
</div>
)}
<div className="card">
<DataTable id="min-stock" columns={columns} rows={rows}
getRowKey={(r) => r.key} empty="Noch keine Mindestbestände." />