const app = { dom: {}, state: { recipes: [], }, api: { async getRecipes() { try { const response = await fetch('api/get_recipes.php'); const data = await response.json(); if (data.success) { app.state.recipes = data.recipes; } else { console.error('Failed to load recipes:', data.error); app.dom.recipeCardsContainer.innerHTML = '

Error loading recipes.

'; } } catch (error) { console.error('Error:', error); app.dom.recipeCardsContainer.innerHTML = '

Could not connect to the server.

'; } }, async saveRecipe(recipeData) { try { const response = await fetch('api/save_recipe.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(recipeData) }); return await response.json(); } catch (error) { alert('Error: ' + error.message); return { success: false, error: error.message }; } }, async deleteRecipe(id) { try { const response = await fetch('api/delete_recipe.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }); const data = await response.json(); if (data.success) { await app.api.getRecipes(); app.ui.renderRecipeCards(app.state.recipes); app.ui.updateShoppingList(); } else { alert('Failed to delete recipe: ' + data.error); } } catch (error) { alert('Error: ' + error.message); } } }, ui: { renderRecipeCards(recipes) { app.dom.recipeCardsContainer.innerHTML = ''; if (!recipes || recipes.length === 0) { app.dom.recipeCardsContainer.innerHTML = '

Your saved recipes will appear here.

'; return; } recipes.forEach(recipe => { const cardCol = document.createElement('div'); cardCol.className = 'col-12 mb-3'; cardCol.setAttribute('data-id', recipe.id); const card = document.createElement('div'); card.className = 'card h-100'; const cardBody = document.createElement('div'); cardBody.className = 'card-body d-flex flex-column'; const title = document.createElement('h5'); title.className = 'card-title'; title.textContent = recipe.name; const text = document.createElement('p'); text.className = 'card-text text-muted'; text.textContent = `${recipe.ingredients.length} ingredients`; const buttonGroup = document.createElement('div'); buttonGroup.className = 'mt-auto pt-2'; buttonGroup.innerHTML = ` `; cardBody.appendChild(title); cardBody.appendChild(text); cardBody.appendChild(buttonGroup); card.appendChild(cardBody); cardCol.appendChild(card); app.dom.recipeCardsContainer.appendChild(cardCol); }); }, updateShoppingList() { const guestCount = parseInt(app.dom.guestCountInput.value, 10) || 1; const portionsPerGuest = parseInt(app.dom.portionsPerGuestInput.value, 10) || 1; const totalMultiplier = guestCount * portionsPerGuest; const groups = { Weight: { units: ['g', 'kg'], ingredients: new Map() }, Volume: { units: ['ml', 'l'], ingredients: new Map() }, Count: { units: ['piece', 'pack'], ingredients: new Map() }, Other: { units: [], ingredients: new Map() } }; app.state.recipes.forEach(recipe => { if (recipe.ingredients) { recipe.ingredients.forEach(ing => { const ingName = ing.name || ''; const ingUnit = ing.unit || ''; const key = `${ingName.trim().toLowerCase()}|${ingUnit.trim().toLowerCase()}`; let groupName = 'Other'; for (const name in groups) { if (groups[name].units.includes(ingUnit)) { groupName = name; break; } } const existing = groups[groupName].ingredients.get(key); if (existing) { existing.qty += (ing.quantity || 0); } else { groups[groupName].ingredients.set(key, { name: ing.name, qty: (ing.quantity || 0), unit: ing.unit }); } }); } }); let html = ''; let totalIngredients = 0; if (app.state.additionalProducts) { app.state.additionalProducts.forEach(prod => { const key = `${prod.name.trim().toLowerCase()}|${prod.unit.trim().toLowerCase()}`; let groupName = 'Other'; for (const name in groups) { if (groups[name].units.includes(prod.unit)) { groupName = name; break; } } const existing = groups[groupName].ingredients.get(key); if (existing) { existing.qty += prod.quantity; } else { groups[groupName].ingredients.set(key, { name: prod.name, qty: prod.quantity, unit: prod.unit }); } }); } for (const groupName in groups) { const group = groups[groupName]; const ingredientList = Array.from(group.ingredients.values()); if (ingredientList.length > 0) { totalIngredients += ingredientList.length; html += `

${groupName}

`; html += ''; } } if (totalIngredients === 0) { html += '

Your shopping list is empty. Add a recipe, and its ingredients will appear here.

'; } app.dom.shoppingListContainer.innerHTML = html; }, addIngredientRow(ingredient = { name: '', quantity: '', unit: 'g' }) { const row = document.createElement('div'); row.className = 'ingredient-row mb-3'; const units = ['g', 'kg', 'ml', 'l', 'piece', 'pack']; const unitButtons = units.map(u => `` ).join(''); row.innerHTML = `
${unitButtons}
`; app.dom.ingredientsContainer.appendChild(row); }, clearForm() { app.dom.recipeIdInput.value = ''; app.dom.recipeNameInput.value = ''; app.dom.guestCountInput.value = '1'; app.dom.ingredientsContainer.innerHTML = ''; app.ui.addIngredientRow(); app.dom.newRecipeBtn.textContent = 'Save Recipe'; app.dom.cancelEditBtn.style.display = 'none'; document.getElementById('recipe-form-modal-label').textContent = 'Add a Recipe'; }, populateFormForEdit(recipeId) { const recipe = app.state.recipes.find(r => r.id == recipeId); if (!recipe) return; app.dom.recipeIdInput.value = recipe.id; app.dom.recipeNameInput.value = recipe.name; app.dom.guestCountInput.value = recipe.guests; app.dom.ingredientsContainer.innerHTML = ''; if (recipe.ingredients) { recipe.ingredients.forEach(ing => app.ui.addIngredientRow(ing)); } else { app.ui.addIngredientRow(); } app.dom.newRecipeBtn.textContent = 'Update Recipe'; app.dom.cancelEditBtn.style.display = 'block'; document.getElementById('recipe-form-modal-label').textContent = 'Edit Recipe'; app.dom.recipeFormModal.show(); app.dom.recipeNameInput.focus(); }, getRecipeDataFromForm() { const recipeName = app.dom.recipeNameInput.value.trim(); const guests = parseInt(app.dom.guestCountInput.value, 10) || 0; const ingredients = []; const rows = app.dom.ingredientsContainer.querySelectorAll('.ingredient-row'); rows.forEach(row => { const name = row.querySelector('input[placeholder="Ingredient Name"]').value.trim(); const qty = parseFloat(row.querySelector('input[placeholder="Qty"]').value); const activeButton = row.querySelector('.unit-selector .btn-secondary'); const unit = activeButton ? activeButton.textContent.trim() : 'g'; if (name && !isNaN(qty) && qty > 0) { ingredients.push({ name, quantity: qty, unit }); } }); if (recipeName && guests > 0 && ingredients.length > 0) { return { name: recipeName, guests, ingredients }; } return null; }, createSnowflakes() { const snowContainer = document.getElementById('snow-container'); if (!snowContainer) return; snowContainer.innerHTML = ''; const numberOfSnowflakes = 50; for (let i = 0; i < numberOfSnowflakes; i++) { const snowflake = document.createElement('div'); snowflake.className = 'snowflake'; const size = Math.random() * 4 + 2; snowflake.style.width = `${size}px`; snowflake.style.height = `${size}px`; snowflake.style.left = Math.random() * 100 + 'vw'; const animationDuration = Math.random() * 5 + 5; snowflake.style.animationDuration = `${animationDuration}s`; const animationDelay = Math.random() * 5; snowflake.style.animationDelay = `${animationDelay}s`; snowflake.style.opacity = Math.random() * 0.7 + 0.3; snowContainer.appendChild(snowflake); } } }, events: { attachEventListeners() { app.dom.addIngredientBtn.addEventListener('click', () => app.ui.addIngredientRow()); app.dom.ingredientsContainer.addEventListener('click', function(e) { if (e.target.classList.contains('remove-ingredient')) { e.target.closest('.ingredient-row').remove(); } else if (e.target.classList.contains('unit-btn')) { const group = e.target.closest('.unit-selector'); if (group) { group.querySelectorAll('.unit-btn').forEach(btn => { btn.classList.remove('btn-secondary'); btn.classList.add('btn-outline-secondary'); }); e.target.classList.remove('btn-outline-secondary'); e.target.classList.add('btn-secondary'); } } }); app.dom.newRecipeBtn.addEventListener('click', async function() { const recipeData = app.ui.getRecipeDataFromForm(); if (!recipeData) { alert('Please fill out the recipe name, guests, and at least one ingredient before saving.'); return; } const recipeId = app.dom.recipeIdInput.value; if (recipeId) { recipeData.id = recipeId; } const data = await app.api.saveRecipe(recipeData); if (data.success && data.recipe) { const savedRecipe = data.recipe; if (recipeId) { const index = app.state.recipes.findIndex(r => r.id == recipeId); if (index !== -1) { app.state.recipes[index] = savedRecipe; } const card = app.dom.recipeCardsContainer.querySelector(`[data-id="${recipeId}"]`); if (card) { card.querySelector('.card-title').textContent = savedRecipe.name; card.querySelector('.card-text').textContent = `${savedRecipe.ingredients.length} ingredients`; } } else { app.state.recipes.unshift(savedRecipe); app.ui.renderRecipeCards(app.state.recipes); } app.ui.updateShoppingList(); app.ui.clearForm(); app.dom.recipeFormModal.hide(); } else { alert('Failed to save recipe: ' + data.error); } }); app.dom.recipeCardsContainer.addEventListener('click', function(e) { const target = e.target; const card = target.closest('.col-12[data-id]'); if (!card) return; const recipeId = card.getAttribute('data-id'); if (target.classList.contains('delete-recipe')) { if (confirm('Are you sure you want to delete this recipe?')) { app.api.deleteRecipe(recipeId); } } if (target.classList.contains('edit-recipe')) { app.ui.populateFormForEdit(recipeId); } }); app.dom.shoppingListContainer.addEventListener('change', function(e) { if (e.target.matches('.form-check-input')) { const listItem = e.target.closest('.list-group-item'); if (listItem) { listItem.classList.toggle('checked', e.target.checked); } } }); app.dom.guestCountInput.addEventListener('input', app.ui.updateShoppingList); app.dom.portionsPerGuestInput.addEventListener('input', app.ui.updateShoppingList); app.dom.cancelEditBtn.addEventListener('click', function() { app.ui.clearForm(); app.dom.recipeFormModal.hide(); }); app.dom.recipeSearchInput.addEventListener('input', function() { const searchTerm = app.dom.recipeSearchInput.value.toLowerCase(); const filteredRecipes = app.state.recipes.filter(recipe => recipe.name.toLowerCase().includes(searchTerm)); app.ui.renderRecipeCards(filteredRecipes); }); app.dom.printShoppingListBtn.addEventListener('click', function() { window.print(); }); app.dom.addProductBtn.addEventListener('click', () => { const name = prompt('Enter product name:'); if (!name) return; const quantity = parseFloat(prompt('Enter quantity:')); if (isNaN(quantity) || quantity <= 0) { alert('Please enter a valid quantity.'); return; } const unit = prompt('Enter unit (e.g., g, kg, ml, l, piece, pack):'); if (!unit) return; if (!app.state.additionalProducts) { app.state.additionalProducts = []; } app.state.additionalProducts.push({ name: name.trim(), quantity: quantity, unit: unit.trim() }); app.ui.updateShoppingList(); }); document.getElementById('recipe-form-modal').addEventListener('show.bs.modal', function () { if (!app.dom.recipeIdInput.value) { app.ui.clearForm(); } }); } }, init() { app.dom = { recipeNameInput: document.getElementById('recipeName'), guestCountInput: document.getElementById('guestCount'), portionsPerGuestInput: document.getElementById('portionsPerGuest'), ingredientsContainer: document.getElementById('ingredients-container'), addIngredientBtn: document.getElementById('add-ingredient'), newRecipeBtn: document.getElementById('new-recipe-btn'), cancelEditBtn: document.getElementById('cancel-edit-btn'), shoppingListContainer: document.getElementById('shopping-list-container'), recipeCardsContainer: document.getElementById('recipe-cards-container'), recipeIdInput: document.getElementById('recipeId'), recipeSearchInput: document.getElementById('recipe-search'), addProductBtn: document.getElementById('add-product-btn'), printShoppingListBtn: document.getElementById('print-shopping-list-btn'), recipeFormModal: new bootstrap.Modal(document.getElementById('recipe-form-modal')) }; app.ui.createSnowflakes(); app.events.attachEventListeners(); app.dom.cancelEditBtn.style.display = 'none'; app.ui.addIngredientRow(); app.api.getRecipes().then(() => { app.ui.renderRecipeCards(app.state.recipes); app.ui.updateShoppingList(); }); } }; document.addEventListener('DOMContentLoaded', app.init);