106 lines
3.5 KiB
JavaScript
106 lines
3.5 KiB
JavaScript
import { useState, useMemo } from 'react';
|
||
|
||
function parseIngredientString(raw) {
|
||
if (typeof raw !== 'string') return raw;
|
||
const m = raw.match(/^([\d½¼¾⅓⅔.,–\-]+\s*(?:g|kg|ml|l|EL|TL|Stück|Packung|Prise|Bund|Dose|Scheibe|cm|dick)?)\s*(.*)/i);
|
||
if (m) {
|
||
const amount = m[1].trim();
|
||
const rest = m[2].trim();
|
||
const unitMatch = rest.match(/^((?:g|kg|ml|l|EL|TL|Stück|Packung|Prise|Bund|Dose|Scheibe|dick)s?)\s+(.*)/i);
|
||
if (unitMatch) {
|
||
return { amount, unit: unitMatch[1], name: unitMatch[2] };
|
||
}
|
||
return { amount, unit: '', name: rest };
|
||
}
|
||
return { amount: '', unit: '', name: raw };
|
||
}
|
||
|
||
function normalizeIngredients(ingredients) {
|
||
return ingredients.map(ing =>
|
||
typeof ing === 'string' ? parseIngredientString(ing) : ing
|
||
);
|
||
}
|
||
|
||
export default function IngredientList({ ingredients, editable, onChange }) {
|
||
const [checked, setChecked] = useState({});
|
||
|
||
const normalized = useMemo(() => normalizeIngredients(ingredients), [ingredients]);
|
||
|
||
const toggle = (i) => setChecked(prev => ({ ...prev, [i]: !prev[i] }));
|
||
|
||
const updateIngredient = (i, field, value) => {
|
||
const updated = normalized.map((ing, idx) =>
|
||
idx === i ? { ...ing, [field]: value } : ing
|
||
);
|
||
onChange(updated);
|
||
};
|
||
|
||
const addIngredient = () => {
|
||
onChange([...normalized, { name: '', amount: '', unit: '' }]);
|
||
};
|
||
|
||
const removeIngredient = (i) => {
|
||
onChange(normalized.filter((_, idx) => idx !== i));
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<h3 className="font-semibold text-lg mb-3">Zutaten</h3>
|
||
<ul className="space-y-2">
|
||
{normalized.map((ing, i) => (
|
||
<li key={i} className="flex items-center gap-2">
|
||
{!editable && (
|
||
<input
|
||
type="checkbox"
|
||
checked={!!checked[i]}
|
||
onChange={() => toggle(i)}
|
||
className="w-4 h-4 accent-blue-600"
|
||
/>
|
||
)}
|
||
{editable ? (
|
||
<>
|
||
<input
|
||
type="text"
|
||
value={ing.amount}
|
||
onChange={e => updateIngredient(i, 'amount', e.target.value)}
|
||
placeholder="Menge"
|
||
className="w-16 px-2 py-1 border border-gray-300 rounded text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={ing.unit}
|
||
onChange={e => updateIngredient(i, 'unit', e.target.value)}
|
||
placeholder="Einheit"
|
||
className="w-14 px-2 py-1 border border-gray-300 rounded text-sm"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={ing.name}
|
||
onChange={e => updateIngredient(i, 'name', e.target.value)}
|
||
placeholder="Zutat"
|
||
className="flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
|
||
/>
|
||
<button onClick={() => removeIngredient(i)} className="text-red-400 hover:text-red-600 text-lg">×</button>
|
||
</>
|
||
) : (
|
||
<span className={`${checked[i] ? 'line-through text-gray-400' : ''}`}>
|
||
{ing.amount && `${ing.amount} `}
|
||
{ing.unit && `${ing.unit} `}
|
||
{ing.name}
|
||
</span>
|
||
)}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
{editable && (
|
||
<button
|
||
onClick={addIngredient}
|
||
className="mt-2 text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||
>
|
||
+ Zutat hinzufügen
|
||
</button>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|