Dashboard kennt die Einkaufsliste jetzt ganz
Der Knopf im Rezept schrieb die fehlenden Zutaten weg, aber auf der Uebersicht blieb davon nichts zu sehen: Die Karte "Einkaufsliste" holte die drei GERECHNETEN Quellen einzeln und wusste von den gespeicherten Eintraegen nichts. Wer die Uebersicht als Zettel benutzt, hat sie schlicht nicht gesehen. Die Karte holt jetzt alles mit einem Aufruf und zeigt oben den Abschnitt "Selbst draufgesetzt" - eigene Eintraege und die aus einem Rezept uebernommenen, mit Herkunft und Menge. Abgehakte verschwinden dort, die Karte ist zum Einkaufen da und nicht zum Nachlesen. Das Abhaken war in der Karte reiner Sitzungszustand und damit nach dem Neuladen weg. Es benutzt jetzt dieselben Schluessel und dieselben Routen wie die volle Seite: Der Haken wandert sofort, speichert im Hintergrund, und springt zurueck, wenn das schiefgeht. Dazu die Kennzahl "Nachzukaufen": Sie zaehlte nur die gerechneten Zeilen und meldete "alles erreicht", waehrend auf der Liste fuenf Posten standen. Offene eigene Eintraege zaehlen jetzt mit, abgehakte nicht - die sind gekauft. Von Hand geprueft: Zwei Eintraege angelegt, Kennzahl springt auf 2, die Karte zeigt beide samt "2 Packungen", und ein Haken aus der Karte steht anschliessend in der Datenbank. 1 neuer Test, Suite bei 517. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b903de3841
commit
2e610677aa
@@ -316,23 +316,59 @@ function KarteAblauf({ modus = "alle" }) {
|
||||
*/
|
||||
function KarteEinkaufsliste({ props: karteProps }) {
|
||||
const ortId = karteProps?.location_id || "";
|
||||
const [erledigt, setErledigt] = useState({}); // abgehakte Zeilen (nur in dieser Sitzung)
|
||||
const [erledigt, setErledigt] = useState(null); // null = noch nicht geladen
|
||||
const [eintraege, setEintraege] = useState([]);
|
||||
// Ein Aufruf statt drei, und er bringt beides mit: die gerechneten Bedarfe
|
||||
// UND die selbst draufgesetzten Eintraege samt dauerhaft gesetzter Haken.
|
||||
// Vorher kannte die Karte nur die gerechnete Seite - was aus einem Rezept
|
||||
// uebernommen wurde, stand nur im Tab und hier nicht.
|
||||
const { daten, fehler } = useDaten(async () => {
|
||||
const [produkte, gruppen, orte] = await Promise.all([
|
||||
api.shoppingList(), api.groupShoppingList(), api.shoppingByLocation(),
|
||||
]);
|
||||
return { produkte, gruppen, orte };
|
||||
const alles = await api.shoppingAll();
|
||||
return {
|
||||
produkte: alles.products || [], gruppen: alles.groups || [],
|
||||
orte: alles.by_location || [], eintraege: alles.entries || [],
|
||||
checked: alles.checked || [],
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!daten) return;
|
||||
setErledigt(Object.fromEntries((daten.checked || []).map((k) => [k, true])));
|
||||
setEintraege(daten.eintraege || []);
|
||||
}, [daten]);
|
||||
|
||||
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
|
||||
if (!daten) return <CardSkeleton variant="liste" />;
|
||||
if (!daten || erledigt == null) return <CardSkeleton variant="liste" />;
|
||||
|
||||
const orte = ortId ? daten.orte.filter((o) => o.location_id === ortId) : daten.orte;
|
||||
const gesamtLeer = !daten.produkte.length && !daten.gruppen.length;
|
||||
const orteLeer = orte.every((o) => !o.products.length && !o.groups.length);
|
||||
if (gesamtLeer && orteLeer) return <div className="empty">Alle Mindestbestände erreicht.</div>;
|
||||
const offeneEintraege = eintraege.filter((e) => !e.done);
|
||||
if (gesamtLeer && orteLeer && eintraege.length === 0) {
|
||||
return <div className="empty">Alle Mindestbestände erreicht.</div>;
|
||||
}
|
||||
|
||||
const abhaken = (key, val) => setErledigt((e) => ({ ...e, [key]: val }));
|
||||
// Der Haken wandert sofort und wird im Hintergrund gespeichert - genau wie auf
|
||||
// der vollen Seite, mit denselben Schluesseln. Schlaegt es fehl, springt er
|
||||
// zurueck, statt eine Erledigung vorzutaeuschen.
|
||||
const abhaken = async (key, val) => {
|
||||
setErledigt((e) => ({ ...e, [key]: val }));
|
||||
try {
|
||||
if (val) await api.setShoppingCheck(key);
|
||||
else await api.clearShoppingCheck(key);
|
||||
} catch {
|
||||
setErledigt((e) => ({ ...e, [key]: !val }));
|
||||
}
|
||||
};
|
||||
|
||||
const eintragAbhaken = async (eintrag, val) => {
|
||||
setEintraege((es) => es.map((e) => (e.id === eintrag.id ? { ...e, done: val } : e)));
|
||||
try {
|
||||
await api.updateShoppingEntry(eintrag.id, { done: val });
|
||||
} catch {
|
||||
setEintraege((es) => es.map((e) => (e.id === eintrag.id ? { ...e, done: !val } : e)));
|
||||
}
|
||||
};
|
||||
|
||||
// Ein Abschnitt (Gesamt oder ein Ort) als abhakbare Liste – wie die volle Seite.
|
||||
// `prefix` macht die Schlüssel über die Abschnitte hinweg eindeutig. Die
|
||||
@@ -377,6 +413,28 @@ function KarteEinkaufsliste({ props: karteProps }) {
|
||||
|
||||
return (
|
||||
<div className="card-stack">
|
||||
{offeneEintraege.length > 0 && (
|
||||
<div>
|
||||
<div className="muted small" style={{ fontWeight: 600 }}>Selbst draufgesetzt</div>
|
||||
<ul className="checklist">
|
||||
{offeneEintraege.map((e) => (
|
||||
<li key={`e${e.id}`}>
|
||||
<label>
|
||||
<input type="checkbox" checked={e.done}
|
||||
onChange={(ev) => eintragAbhaken(e, ev.target.checked)} />
|
||||
{e.art === "group" && <span className="badge accent">Gruppe</span>}
|
||||
{e.art === "text" && <span className="badge">Notiz</span>}
|
||||
<span className="item-name">{e.name}</span>
|
||||
</label>
|
||||
<span className="muted small nowrap">
|
||||
{[e.recipe_name ? `für ${e.recipe_name}` : "", e.menge_text]
|
||||
.filter(Boolean).join(" · ")}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{!gesamtLeer && (
|
||||
<div>
|
||||
<div className="muted small" style={{ fontWeight: 600 }}>Überall</div>
|
||||
|
||||
Reference in New Issue
Block a user