Web: Mindestbestand je Lagerort + Einkaufsliste je Ort

- Neuer LocationMinStock-Editor (Zeilen: Lagerort + Menge, separat speichern).
- Produktformular: Abschnitt „Mindestbestand je Lagerort" (Menge in Artikel-
  einheit), zusaetzlich zum Gesamt-Mindestbestand.
- Gruppen: je Gruppe ein Editor „Mindestbestand je Lagerort".
- Einkaufsliste: Abschnitt „Bedarf: <Lagerort>" mit Produkten und Gruppen, die
  am Ort unter dem dort hinterlegten Mindestbestand liegen.
- API-Methoden set*LocationMinStock und shoppingByLocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-27 07:12:59 +02:00
parent 67bc2dd87d
commit dff7677e50
5 changed files with 199 additions and 3 deletions

View File

@@ -6,12 +6,13 @@ import { amountText, fmt, unitShort } from "../units";
export default function ShoppingList() {
const [items, setItems] = useState([]);
const [groups, setGroups] = useState([]);
const [byLocation, setByLocation] = useState([]);
const [checked, setChecked] = useState({});
const [error, setError] = useState(null);
useEffect(() => {
Promise.all([api.shoppingList(), api.groupShoppingList()])
.then(([p, g]) => { setItems(p); setGroups(g); })
Promise.all([api.shoppingList(), api.groupShoppingList(), api.shoppingByLocation()])
.then(([p, g, l]) => { setItems(p); setGroups(g); setByLocation(l); })
.catch((e) => setError(e.message));
}, []);
@@ -31,6 +32,10 @@ export default function ShoppingList() {
</div>
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
{byLocation.length > 0 && (
<div className="sub" style={{ marginBottom: "var(--sp-2)" }}>Gesamt</div>
)}
<div className="card">
{empty ? (
<div className="empty">Alle Mindestbestände erreicht.</div>
@@ -70,6 +75,50 @@ export default function ShoppingList() {
</ul>
)}
</div>
{byLocation.map((loc) => (
<div key={loc.location_id} style={{ marginTop: "var(--sp-4)" }}>
<div className="sub" style={{ marginBottom: "var(--sp-2)" }}>
<Icon name="location" size={14} /> Bedarf: {loc.location_name}
</div>
<div className="card">
<ul className="checklist">
{loc.groups.map((it) => {
const key = `l${loc.location_id}g${it.group_id}`;
return (
<li key={key} className={checked[key] ? "done" : ""}>
<label>
<input type="checkbox" checked={!!checked[key]} onChange={(e) => toggle(key, e.target.checked)} />
<span className="badge accent">Gruppe</span>
<span className="item-name">{it.name}</span>
</label>
<span className="muted small">
fehlt <strong>{fmt(it.deficit)} {it.unit_name}</strong>{" "}
(Bestand {fmt(it.stock)} / min {fmt(it.min_stock)})
</span>
</li>
);
})}
{loc.products.map((it) => {
const key = `l${loc.location_id}p${it.product_id}`;
return (
<li key={key} className={checked[key] ? "done" : ""}>
<label>
<input type="checkbox" checked={!!checked[key]} onChange={(e) => toggle(key, e.target.checked)} />
<span className="item-name">{it.name}</span>
</label>
<span className="muted small">
fehlt <strong>{fmt(it.deficit)} {it.unit_label}</strong>{" "}
(Bestand {fmt(it.stock)} / min {fmt(it.min_stock)})
</span>
</li>
);
})}
</ul>
</div>
</div>
))}
<p className="muted small">
Das Abhaken hilft beim Einkaufen und wird (noch) nicht dauerhaft gespeichert.
</p>