import { useRef, useState } from 'react'; import { flushSync } from 'react-dom'; import { api } from '../api.js'; import ListDialog from '../components/haushalt/ListDialog.jsx'; import ListPanel, { LISTS } from '../components/haushalt/ListPanel.jsx'; import ListSwitcher from '../components/haushalt/ListSwitcher.jsx'; import ProductsDialog from '../components/haushalt/ProductsDialog.jsx'; import RecipeCheck from '../components/haushalt/RecipeCheck.jsx'; import { isCalm } from '../motion.js'; import useLiveData from '../useLiveData.js'; // Geteilte Listen: regelmäßig nachladen, damit Änderungen der anderen ankommen const REFRESH_SECONDS = 15; const TAB_KEY = 'p42.haushaltTab'; const LIST_KEY = 'p42.haushaltListe'; function loadTab() { try { return localStorage.getItem(TAB_KEY) === 'einkauf' ? 'einkauf' : 'vorrat'; } catch { return 'vorrat'; } } function loadListId() { try { return Number(localStorage.getItem(LIST_KEY)) || null; } catch { return null; } } export default function Haushalt() { const { data, setData, error, busy, run: send } = useLiveData('/api/haushalt', { refresh: REFRESH_SECONDS }); const [tab, setTab] = useState(loadTab); const [listId, setListId] = useState(loadListId); // Dialog für Listen: null = zu, { list: null } = neue Liste, { list } = bearbeiten const [listDialog, setListDialog] = useState(null); const dialog = useRef(null); const listDialogRef = useRef(null); // Jede Änderung liefert den aktuellen Stand aller Listen zurück. // animate: Einträge gleiten an ihren neuen Platz (z. B. abgehakt → nach unten), wo der Browser es kann function run(method, url, body, { animate = false } = {}) { if (!animate || !document.startViewTransition || isCalm()) return send(method, url, body); return send(method, url, body, { apply: (next) => { const root = document.documentElement; root.classList.add('vt-items'); const vt = document.startViewTransition(() => flushSync(() => setData(next))); vt.finished.finally(() => root.classList.remove('vt-items')); }, }); } function chooseTab(next) { setTab(next); try { localStorage.setItem(TAB_KEY, next); } catch { /* ignorieren */ } } function chooseList(id) { setListId(id); try { localStorage.setItem(LIST_KEY, String(id)); } catch { /* ignorieren */ } } function openListDialog(list) { setListDialog({ list }); // Erst nach dem Rendern öffnen, damit der Dialog mit den neuen Werten startet requestAnimationFrame(() => listDialogRef.current?.showModal()); } async function saveList(form) { const editing = listDialog?.list; try { const next = await api(editing ? 'PATCH' : 'POST', editing ? `/api/haushalt/listen/${editing.id}` : '/api/haushalt/listen', form); setData(next); if (next.created) chooseList(next.created); return null; } catch (e) { return e.message; } } async function deleteList(list) { const count = (data?.items || []).filter((i) => i.listId === list.id).length; const what = count ? ` mit ${count} ${count === 1 ? 'Eintrag' : 'Einträgen'}` : ''; if (!window.confirm(`Liste „${list.name}“ samt Vorrat und Einkauf${what} löschen?`)) return false; return Boolean(await run('DELETE', `/api/haushalt/listen/${list.id}`)); } const items = data?.items || []; const lists = data?.lists || []; // Gewählte Liste (z. B. Zuhause, Urlaub); gibt es sie nicht (mehr), die erste sichtbare const current = lists.find((l) => l.id === listId) || lists[0] || null; const mine = current ? items.filter((i) => i.listId === current.id) : []; const byList = { vorrat: mine.filter((i) => i.list === 'vorrat'), einkauf: mine.filter((i) => i.list === 'einkauf'), }; // Alle Zeilen eines Produkts (ein Produkt kann in mehreren Einheiten auf einer Liste stehen) const find = (list, name) => byList[list].filter((i) => i.name.toLocaleLowerCase('de-DE') === name.toLocaleLowerCase('de-DE')); const actions = { add: (list, name, quantity, unit) => run('POST', '/api/haushalt/items', { list, name, quantity, unit, listId: current?.id }), update: (item, patch) => run('PATCH', `/api/haushalt/items/${item.id}`, patch), move: (item) => run('POST', `/api/haushalt/items/${item.id}/move`), check: (item, checked) => run('PATCH', `/api/haushalt/items/${item.id}`, { checked }, { animate: true }), finish: () => run('POST', `/api/haushalt/listen/${current.id}/einkauf-beenden`, undefined, { animate: true }), remove: (item) => run('DELETE', `/api/haushalt/items/${item.id}`), }; return ( <>

Vorrat & Einkauf

Jede Liste hat ihren eigenen Vorrat und Einkauf – z. B. Zuhause und Urlaub. Wird alle {REFRESH_SECONDS} Sekunden aktualisiert.

{data?.products.length > 0 && ( )}
{error &&

{error}

} {!data && !error &&

Lade …

} {data && ( openListDialog(null)} onEdit={openListDialog} busy={busy} /> )} {data && !current && (

Keine Liste, die du sehen kannst. Leg mit „+ Neue Liste“ eine an.

)} {data && current && ( <> run('POST', '/api/haushalt/rezept/uebernehmen', { ...body, listId: current?.id })} />
{Object.entries(LISTS).map(([key, { title }]) => ( ))}
{Object.keys(LISTS).map((list) => (
)} {listDialog && ( setListDialog(null)} /> )} run('DELETE', `/api/haushalt/produkte?name=${encodeURIComponent(name)}`)} /> ); }