Web: Einkaufsliste-Karte zum Abhaken (wie die volle Seite)

Die Karte zeigte die Bedarfe als Tabelle ohne Haekchen. Jetzt als abhakbare
Checklist (Gesamt + je Lagerort) mit Durchstreichen beim Abhaken - dieselbe
Bedienung wie die Einkaufslisten-Seite. Abhaken ist sitzungslokal (wie dort
auch, noch nicht dauerhaft gespeichert).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-30 14:36:04 +02:00
parent 3e10e254c8
commit 55db1d1363

View File

@@ -312,6 +312,7 @@ function KarteAblauf({ modus = "alle" }) {
*/ */
function KarteEinkaufsliste({ props: karteProps }) { function KarteEinkaufsliste({ props: karteProps }) {
const ortId = karteProps?.location_id || ""; const ortId = karteProps?.location_id || "";
const [erledigt, setErledigt] = useState({}); // abgehakte Zeilen (nur in dieser Sitzung)
const { daten, fehler } = useDaten(async () => { const { daten, fehler } = useDaten(async () => {
const [produkte, gruppen, orte] = await Promise.all([ const [produkte, gruppen, orte] = await Promise.all([
api.shoppingList(), api.groupShoppingList(), api.shoppingByLocation(), api.shoppingList(), api.groupShoppingList(), api.shoppingByLocation(),
@@ -327,49 +328,67 @@ function KarteEinkaufsliste({ props: karteProps }) {
const orteLeer = orte.every((o) => !o.products.length && !o.groups.length); const orteLeer = orte.every((o) => !o.products.length && !o.groups.length);
if (gesamtLeer && orteLeer) return <div className="empty">Alle Mindestbestände erreicht.</div>; if (gesamtLeer && orteLeer) return <div className="empty">Alle Mindestbestände erreicht.</div>;
// Ein Abschnitt (Gesamt oder ein Ort). Die Produkt-Formatierung kommt herein, const abhaken = (key, val) => setErledigt((e) => ({ ...e, [key]: val }));
// weil Gesamt-Produkte Gebinde/Basiseinheit tragen, je-Ort-Bedarfe aber schon
// eine fertige Einheit (unit_label). // Ein Abschnitt (Gesamt oder ein Ort) als abhakbare Liste wie die volle Seite.
const abschnitt = (gruppen, produkte, prodWert) => ( // `prefix` macht die Schlüssel über die Abschnitte hinweg eindeutig, `prodInfo`
<div className="table-wrap"> // formatiert die Produktzeile (Gesamt trägt Gebinde/Basiseinheit, je-Ort schon
<table className="table"> // eine fertige Einheit).
<thead><tr><th>Bedarf</th><th className="num">Bestand</th><th className="num">Fehlt</th></tr></thead> const abschnitt = (gruppen, produkte, prefix, prodInfo) => (
<tbody> <ul className="checklist">
{gruppen.map((it) => ( {gruppen.map((it) => {
<tr key={`g${it.group_id}`}> const key = `${prefix}g${it.group_id}`;
<td data-label="Bedarf"><span className="badge accent">Gruppe</span> {it.name}</td> return (
<td data-label="Bestand" className="num">{fmt(it.stock)} {it.unit_name}</td> <li key={key} className={erledigt[key] ? "done" : ""}>
<td data-label="Fehlt" className="num strong">{fmt(it.deficit)} {it.unit_name}</td> <label>
</tr> <input type="checkbox" checked={!!erledigt[key]} onChange={(e) => abhaken(key, e.target.checked)} />
))} <span className="badge accent">Gruppe</span>
{produkte.map((it) => ( <span className="item-name">{it.name}</span>
<tr key={`p${it.product_id}`}> </label>
<td data-label="Bedarf">{it.name}</td> <span className="muted small">
<td data-label="Bestand" className="num">{prodWert(it, "stock")}</td> fehlt <strong>{fmt(it.deficit)} {it.unit_name}</strong>{" "}
<td data-label="Fehlt" className="num strong">{prodWert(it, "deficit")}</td> (Bestand {fmt(it.stock)} / min {fmt(it.min_stock)} {it.unit_name})
</tr> </span>
))} </li>
</tbody> );
</table> })}
</div> {produkte.map((it) => {
const key = `${prefix}p${it.product_id}`;
return (
<li key={key} className={erledigt[key] ? "done" : ""}>
<label>
<input type="checkbox" checked={!!erledigt[key]} onChange={(e) => abhaken(key, e.target.checked)} />
<span className="item-name">{it.name}</span>
</label>
<span className="muted small">{prodInfo(it)}</span>
</li>
);
})}
</ul>
);
const gesamtInfo = (it) => (
<>fehlt <strong>{amountText(it.deficit, it.package_size, it.base_unit)}</strong>{" "}
(Bestand {amountText(it.stock, it.package_size, it.base_unit)})</>
);
const ortInfo = (it) => (
<>fehlt <strong>{fmt(it.deficit)} {it.unit_label}</strong>{" "}
(Bestand {fmt(it.stock)} / min {fmt(it.min_stock)})</>
); );
const gesamtProd = (it, feld) => amountText(it[feld], it.package_size, it.base_unit);
const ortProd = (it, feld) => `${fmt(it[feld])} ${it.unit_label}`;
return ( return (
<div className="card-stack"> <div className="card-stack">
{!gesamtLeer && ( {!gesamtLeer && (
<div> <div>
<div className="muted small" style={{ marginBottom: "var(--sp-1)", fontWeight: 600 }}>Gesamt</div> <div className="muted small" style={{ fontWeight: 600 }}>Gesamt</div>
{abschnitt(daten.gruppen, daten.produkte, gesamtProd)} {abschnitt(daten.gruppen, daten.produkte, "", gesamtInfo)}
</div> </div>
)} )}
{orte.map((o) => ((o.products.length || o.groups.length) ? ( {orte.map((o) => ((o.products.length || o.groups.length) ? (
<div key={o.location_id}> <div key={o.location_id}>
<div className="muted small" style={{ marginBottom: "var(--sp-1)", fontWeight: 600 }}> <div className="muted small" style={{ fontWeight: 600 }}>
<Icon name="location" size={13} /> {o.location_name} <Icon name="location" size={13} /> {o.location_name}
</div> </div>
{abschnitt(o.groups, o.products, ortProd)} {abschnitt(o.groups, o.products, `l${o.location_id}`, ortInfo)}
</div> </div>
) : null))} ) : null))}
</div> </div>