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