/** * Modul: Rezepte (Recipes) * Zweck: Gespeicherte Rezepte verwalten und in den Essensplan uebernehmen */ import { api } from '/api.js'; import { t } from '/i18n.js'; import { openModal as openSharedModal, closeModal as closeSharedModal } from '/components/modal.js'; import { DEFAULT_CATEGORY_NAME, categoryLabel } from '/utils/shopping-categories.js'; let _container = null; const state = { recipes: [], categories: [], }; function mealCategories() { return state.categories.filter((c) => c.name !== 'Haushalt' && c.name !== 'Drogerie'); } async function loadRecipes() { const res = await api.get('/recipes'); state.recipes = res.data; } async function loadCategories() { try { const res = await api.get('/shopping/categories'); state.categories = res.data; } catch { state.categories = []; } } export async function render(container) { _container = container; const page = document.createElement('div'); page.className = 'recipes-page'; const header = document.createElement('div'); header.className = 'recipes-header'; const title = document.createElement('h1'); title.className = 'recipes-header__title'; title.textContent = t('recipes.title'); const addBtn = document.createElement('button'); addBtn.className = 'btn btn--primary'; addBtn.type = 'button'; addBtn.id = 'recipes-add'; addBtn.textContent = t('recipes.addRecipe'); header.append(title, addBtn); const list = document.createElement('div'); list.className = 'recipes-list'; list.id = 'recipes-list'; const fab = document.createElement('button'); fab.className = 'page-fab'; fab.type = 'button'; fab.id = 'recipes-fab'; fab.setAttribute('aria-label', t('recipes.addRecipe')); const fabIcon = document.createElement('i'); fabIcon.dataset.lucide = 'plus'; fabIcon.setAttribute('aria-hidden', 'true'); fab.appendChild(fabIcon); page.append(header, list, fab); container.replaceChildren(page); if (window.lucide) window.lucide.createIcons(); await Promise.all([loadRecipes(), loadCategories()]); renderRecipeList(); addBtn.addEventListener('click', () => openRecipeModal('create')); fab.addEventListener('click', () => openRecipeModal('create')); list.addEventListener('click', async (e) => { const actionBtn = e.target.closest('[data-action]'); if (!actionBtn) return; const recipeId = Number(actionBtn.dataset.id); const recipe = state.recipes.find((r) => r.id === recipeId); if (!recipe) return; if (actionBtn.dataset.action === 'edit') { openRecipeModal('edit', recipe); return; } if (actionBtn.dataset.action === 'delete') { await removeRecipe(recipe); return; } if (actionBtn.dataset.action === 'duplicate') { await duplicateRecipe(recipe); return; } if (actionBtn.dataset.action === 'add-to-meals') { window.oikos?.navigate(`/meals?recipe=${recipe.id}`); } }); } function renderRecipeList() { const list = _container.querySelector('#recipes-list'); if (!list) return; list.replaceChildren(); if (!state.recipes.length) { const empty = document.createElement('div'); empty.className = 'empty-state'; const emptyTitle = document.createElement('div'); emptyTitle.className = 'empty-state__title'; emptyTitle.textContent = t('recipes.emptyTitle'); const emptyDesc = document.createElement('div'); emptyDesc.className = 'empty-state__description'; emptyDesc.textContent = t('recipes.emptyDescription'); empty.append(emptyTitle, emptyDesc); list.appendChild(empty); return; } for (const recipe of state.recipes) { const card = document.createElement('article'); card.className = 'recipe-card'; card.dataset.id = String(recipe.id); const h = document.createElement('h2'); h.className = 'recipe-card__title'; h.textContent = recipe.title; card.appendChild(h); if (recipe.notes) { const notes = document.createElement('p'); notes.className = 'recipe-card__notes'; notes.textContent = recipe.notes; card.appendChild(notes); } if (recipe.recipe_url) { const link = document.createElement('a'); link.className = 'btn btn--ghost'; link.href = recipe.recipe_url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = t('recipes.openLink'); card.appendChild(link); } const ingredients = recipe.ingredients ?? []; if (ingredients.length) { const ul = document.createElement('ul'); ul.className = 'recipe-card__ingredients'; for (const ing of ingredients) { const li = document.createElement('li'); li.className = 'recipe-card__ingredient'; const qty = ing.quantity ? `${ing.quantity} ยท ` : ''; li.textContent = `${qty}${ing.name}`; ul.appendChild(li); } card.appendChild(ul); } const actions = document.createElement('div'); actions.className = 'recipe-card__actions'; const addToMeals = document.createElement('button'); addToMeals.className = 'btn btn--secondary'; addToMeals.type = 'button'; addToMeals.dataset.action = 'add-to-meals'; addToMeals.dataset.id = String(recipe.id); addToMeals.textContent = t('recipes.addToMeals'); const edit = document.createElement('button'); edit.className = 'btn btn--secondary'; edit.type = 'button'; edit.dataset.action = 'edit'; edit.dataset.id = String(recipe.id); edit.textContent = t('common.edit'); const del = document.createElement('button'); del.className = 'btn btn--danger'; del.type = 'button'; del.dataset.action = 'delete'; del.dataset.id = String(recipe.id); del.textContent = t('common.delete'); const duplicate = document.createElement('button'); duplicate.className = 'btn btn--secondary'; duplicate.type = 'button'; duplicate.dataset.action = 'duplicate'; duplicate.dataset.id = String(recipe.id); duplicate.textContent = t('recipes.duplicate'); actions.append(addToMeals, edit, duplicate, del); card.appendChild(actions); list.appendChild(card); } } function buildIngredientRow(name, qty, category = DEFAULT_CATEGORY_NAME) { const categories = mealCategories(); const resolvedCategory = categories.some((c) => c.name === category) ? category : (categories[0]?.name ?? DEFAULT_CATEGORY_NAME); const row = document.createElement('div'); row.className = 'recipe-ingredient-row'; const nameInput = document.createElement('input'); nameInput.type = 'text'; nameInput.className = 'form-input recipe-ingredient-row__name'; nameInput.placeholder = t('meals.ingredientNamePlaceholder'); nameInput.value = name; const qtyInput = document.createElement('input'); qtyInput.type = 'text'; qtyInput.className = 'form-input recipe-ingredient-row__qty'; qtyInput.placeholder = t('meals.ingredientQtyPlaceholder'); qtyInput.value = qty; const catSelect = document.createElement('select'); catSelect.className = 'form-input recipe-ingredient-row__cat'; catSelect.setAttribute('aria-label', t('meals.ingredientCategoryLabel')); if (categories.length) { for (const c of categories) { const opt = document.createElement('option'); opt.value = c.name; opt.textContent = categoryLabel(c.name); if (c.name === resolvedCategory) opt.selected = true; catSelect.appendChild(opt); } } else { const opt = document.createElement('option'); opt.value = DEFAULT_CATEGORY_NAME; opt.textContent = t('meals.ingredientCategoryDefault'); opt.selected = true; catSelect.appendChild(opt); } const removeBtn = document.createElement('button'); removeBtn.className = 'recipe-ingredient-row__remove'; removeBtn.dataset.action = 'remove-ingredient'; removeBtn.type = 'button'; removeBtn.setAttribute('aria-label', t('meals.removeIngredient')); const icon = document.createElement('i'); icon.dataset.lucide = 'x'; icon.style.cssText = 'width:14px;height:14px;'; icon.setAttribute('aria-hidden', 'true'); removeBtn.appendChild(icon); row.append(nameInput, qtyInput, catSelect, removeBtn); return row; } function openRecipeModal(mode, recipe = null) { const isEdit = mode === 'edit'; openSharedModal({ title: isEdit ? t('recipes.editRecipe') : t('recipes.addRecipe'), size: 'md', content: `