import { useRef, useState } from 'react'; import { personName } from '../../api.js'; import { fmtItems, parseQty, round2, STEP } from '../../units.js'; import ProductInput from './ProductInput.jsx'; import UnitSelect from './UnitSelect.jsx'; // Die beiden Bereiche einer Liste: Vorrat (was da ist) und Einkaufen (was fehlt) export const LISTS = { vorrat: { title: 'Vorrat', lead: 'Was da ist', empty: 'Noch nichts im Vorrat eingetragen.' }, einkauf: { title: 'Einkaufen', lead: 'Was besorgt werden muss', empty: 'Nichts zu besorgen.' }, }; export default function ListPanel({ list, items, products, find, actions, busy, hidden }) { const { title, lead, empty } = LISTS[list]; const other = list === 'vorrat' ? 'einkauf' : 'vorrat'; // Einkauf: Abgehaktes rutscht nach unten und wandert erst mit "Einkauf beenden" in den Vorrat const open = items.filter((i) => !i.checked); const inCart = items.filter((i) => i.checked); // Zusatzinfo in den Vorschlägen: steht das Produkt schon irgendwo? function hint(name) { const here = find(list, name); if (here.length) return `schon da: ${fmtItems(here)}`; const there = find(other, name); if (there.length) return `${list === 'vorrat' ? 'auf Einkaufsliste' : 'im Vorrat'}: ${fmtItems(there)}`; return null; } return (

{title}

{lead}
{items.length === 0 ? (

{empty}

) : ( )} {inCart.length > 0 && (
{inCart.length} {inCart.length === 1 ? 'Produkt' : 'Produkte'} im Wagen
)}
); } function AddForm({ list, products, hint, onAdd, busy }) { const [name, setName] = useState(''); const [quantity, setQuantity] = useState('1'); const [unit, setUnit] = useState('stk'); const [invalid, setInvalid] = useState(false); const nameInput = useRef(null); async function submit(e) { e.preventDefault(); const n = name.trim(); const q = parseQty(quantity, unit); setInvalid(q == null); if (!n || q == null) return; if (await onAdd(list, n, q, unit)) { setName(''); setQuantity('1'); nameInput.current?.focus(); } } return (
{ setQuantity(e.target.value); setInvalid(false); }} aria-invalid={invalid || undefined} aria-describedby={invalid ? `qty-error-${list}` : undefined} />
{invalid && (

{unit === 'stk' ? 'Anzahl als ganze Zahl von 1 bis 999 eingeben.' : 'Menge von 0,01 bis 999 eingeben, z. B. 1,5.'}

)} ); } // Menge direkt eintippen; gespeichert wird beim Verlassen des Felds oder mit Enter function QuantityInput({ item, label, onCommit, disabled }) { const shown = String(item.quantity).replace('.', ','); const [draft, setDraft] = useState(null); function commit() { if (draft == null) return; const q = parseQty(draft, item.unit); setDraft(null); if (q != null && q !== item.quantity) onCommit(q); } return ( setDraft(e.target.value)} onBlur={commit} onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur(); if (e.key === 'Escape') setDraft(null); }} aria-label={`Menge ${label}`} /> ); } function ItemRow({ item, list, other, actions, busy }) { const isShopping = list === 'einkauf'; const step = STEP[item.unit]; const last = item.quantity - step <= 0; // Bei gleichem Produkt in mehreren Einheiten die Einheit mit ansagen const label = item.unit === 'stk' ? item.name : `${item.name} (${item.unit})`; return (
  • {isShopping && ( actions.check(item, !item.checked)} aria-label={item.checked ? `${label} im Wagen – Haken entfernen` : `${label} abhaken (im Wagen)`} title={item.checked ? 'Im Wagen – zum Zurücknehmen klicken' : 'Abhaken: kommt mit „Einkauf beenden“ in den Vorrat'} /> )}
    {item.name} {other.length > 0 && `${isShopping ? 'im Vorrat' : 'auf Einkaufsliste'}: ${fmtItems(other)} · `} ✎ {personName(item.updatedBy)}
    actions.update(item, { quantity })} />
    actions.update(item, { unit })} disabled={busy} compact label={`Einheit ${item.name}`} />
    {!isShopping && ( )}
  • ); }