diff --git a/app/templates/recipe_detail.html b/app/templates/recipe_detail.html
index 7b9d652..d89dd7a 100755
--- a/app/templates/recipe_detail.html
+++ b/app/templates/recipe_detail.html
@@ -24,6 +24,9 @@
Aus
{% if not is_shared %}
+
+ 🛒 Auf Einkaufsliste
+
📖 Alle Rezepte
@@ -65,6 +68,9 @@
{% if not is_shared %}
+
+ 🛒 Auf Einkaufsliste
+
🔗 Rezept teilen
diff --git a/app/templates/shopping_list.html b/app/templates/shopping_list.html
index 1b7c009..2ee0bf0 100644
--- a/app/templates/shopping_list.html
+++ b/app/templates/shopping_list.html
@@ -541,6 +541,9 @@
📅 Aus Wochenplan holen
+
+ 📖 Aus Kochbuch holen
+
🧹 Erledigte löschen
@@ -573,6 +576,9 @@
📅 Aus Wochenplan holen
+
+ 📖 Aus Kochbuch holen
+
🧹 Erledigte löschen
@@ -616,10 +622,15 @@
🎉
Alles eingekauft!
-
Deine Einkaufsliste ist leer. Tippe unten rechts auf das + , um einen neuen Eintrag zu notieren, oder übernimm Zutaten direkt aus dem Wochenplan.
-
- 📅 Aus Wochenplan importieren
-
+
Deine Einkaufsliste ist leer. Tippe unten rechts auf das + , um einen neuen Eintrag zu notieren, oder übernimm Zutaten direkt aus dem Wochenplan oder Kochbuch.
+
+
+ 📅 Aus Wochenplan holen
+
+
+ 📖 Aus Kochbuch holen
+
+
@@ -816,6 +827,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Schließen
+
+
+ Ausgewählte übernehmen
+
+
+
+
+
+
@@ -1813,6 +1849,398 @@
}
}
+ // ==========================================
+ // MODAL: ZUTATEN AUS KOCHBUCH HOLEN
+ // ==========================================
+ let allCookbookRecipes = null;
+ let selectedCookbookRecipes = [];
+
+ const CHAPTER_NAMES = {
+ 1: "Suppen & Eintöpfe",
+ 2: "Traditionell & Basics",
+ 3: "Pasta & Nudelgerichte",
+ 4: "Kartoffeln",
+ 5: "Reis & Getreide",
+ 6: "Teig & Gebackenes",
+ 7: "Asiatische Küche",
+ 8: "Süßspeisen",
+ 9: "Desserts",
+ 10: "Kuchen & Gebäck"
+ };
+
+ function cleanIngredientName(name) {
+ if (!name) return '';
+ let str = name.trim().replace(/^[ \-\*\s]+/, '');
+ str = str.replace(/\s*\([^)]*\)/g, '');
+ if (str.includes(',')) {
+ const parts = str.split(',');
+ if (parts[1].trim().split(/\s+/).length <= 4) {
+ str = parts[0];
+ }
+ }
+ str = str.replace(/[\s.,;:]+$/, '').replace(/\s+/g, ' ').trim();
+ if (str && str[0] === str[0].toLowerCase()) {
+ str = str[0].toUpperCase() + str.slice(1);
+ }
+ return str;
+ }
+
+ function getNormalizedIngredientKey(name) {
+ if (!name) return "";
+ let str = name.toLowerCase().replace(/^[ \-\*\s]+/, "").replace(/\s*\([^)]*\)/g, "");
+ if (str.includes(",")) str = str.split(",")[0];
+ str = str.replace(/[\s.,;:]+$/, "").replace(/\s+/g, " ").trim();
+ if (str.endsWith("en") && str.length > 5) str = str.slice(0, -2);
+ else if (str.endsWith("n") && str.length > 4 && !str.endsWith("in")) str = str.slice(0, -1);
+ return str;
+ }
+
+ function findMatchingOpenShoppingItem(name) {
+ const k = getNormalizedIngredientKey(name);
+ if (!k) return null;
+ return allItems.find(it => !it.checked && getNormalizedIngredientKey(it.item_name) === k) || null;
+ }
+
+ async function openCookbookImportModal(preselectedRecipeId = null) {
+ openModal('modal-import-cookbook');
+ const body = document.getElementById('cookbook-import-body');
+ const confirmBtn = document.getElementById('btn-confirm-import-cookbook');
+ if (confirmBtn) {
+ confirmBtn.disabled = true;
+ confirmBtn.textContent = 'Ausgewählte übernehmen';
+ }
+
+ if (!allCookbookRecipes) {
+ body.innerHTML = `
+
+
+
Rezepte werden geladen...
+
+ `;
+ try {
+ const res = await fetch('/api/recipes');
+ if (!res.ok) throw new Error('Fehler beim Laden der Rezepte');
+ allCookbookRecipes = await res.json();
+ } catch (err) {
+ console.error('Fehler beim Laden der Rezepte:', err);
+ body.innerHTML = `
+
+ Fehler beim Laden der Rezepte. Bitte stelle sicher, dass du online bist.
+
+ `;
+ return;
+ }
+ }
+
+ if (preselectedRecipeId) {
+ selectedCookbookRecipes = [];
+ }
+
+ renderCookbookImportUI();
+
+ if (preselectedRecipeId) {
+ await addCookbookRecipeToSelection(preselectedRecipeId);
+ }
+ }
+
+ function renderCookbookImportUI() {
+ const body = document.getElementById('cookbook-import-body');
+ if (!body) return;
+
+ const recipesByChapter = {};
+ allCookbookRecipes.forEach(r => {
+ const ch = r.chapter_number || 1;
+ if (!recipesByChapter[ch]) recipesByChapter[ch] = [];
+ recipesByChapter[ch].push(r);
+ });
+
+ const sortedChapters = Object.keys(recipesByChapter).map(Number).sort((a,b) => a - b);
+
+ let optionsHtml = '-- Rezept auswählen... -- ';
+ sortedChapters.forEach(ch => {
+ const chName = CHAPTER_NAMES[ch] || `Kapitel ${ch}`;
+ optionsHtml += ``;
+ recipesByChapter[ch].forEach(r => {
+ const sub = r.subtitle ? ` (${r.subtitle})` : '';
+ optionsHtml += `${escapeHtml(r.title)}${escapeHtml(sub)} `;
+ });
+ optionsHtml += ` `;
+ });
+
+ body.innerHTML = `
+
+
+
+ 📖 Rezept aus dem Kochbuch wählen:
+
+ ${allCookbookRecipes.length} Rezepte
+
+
+
+
+
+ ${optionsHtml}
+
+
+
+
+
+ ${renderSelectedCookbookRecipeCards()}
+
+ `;
+
+ updateCookbookConfirmBtn();
+ }
+
+ function renderSelectedCookbookRecipeCards() {
+ if (selectedCookbookRecipes.length === 0) {
+ return `
+
+
📖
+
Noch kein Rezept ausgewählt
+
+ Wähle oben ein Rezept aus, um dessen Zutaten auf deine Einkaufsliste zu setzen. Mengen von bereits vorhandenen Zutaten werden automatisch addiert – ohne Dopplungen!
+
+
+ `;
+ }
+
+ return selectedCookbookRecipes.map(r => {
+ const ingredients = r.parsed_ingredients || [];
+ const chName = CHAPTER_NAMES[r.chapter_number] || `Kapitel ${r.chapter_number || 1}`;
+
+ let itemsHtml = '';
+ if (ingredients.length === 0) {
+ itemsHtml = `Keine Zutaten für dieses Rezept hinterlegt.
`;
+ } else {
+ itemsHtml = ingredients.map(ing => {
+ if (ing.is_header) {
+ return `${escapeHtml(ing.raw)}
`;
+ }
+
+ const cleanName = cleanIngredientName(ing.name || ing.raw || '');
+ if (!cleanName) return '';
+
+ const amtStr = formatAmount(ing.amount_formatted || ing.amount_str || ing.amount, ing.unit);
+ const match = findMatchingOpenShoppingItem(cleanName);
+
+ return `
+
+
+
+ ${amtStr ? `${escapeHtml(amtStr)} ` : ''}${escapeHtml(cleanName)}
+
+ ${match ? `
+
+ ➕ wird addiert (${escapeHtml(match.amount ? match.amount + ' ' + (match.unit || '') : 'auf Liste')})
+
+ ` : ''}
+
+ `;
+ }).join('');
+ }
+
+ return `
+
+
+
+
+ 🍳 ${escapeHtml(r.title)}
+
+ ${r.subtitle ? `
${escapeHtml(r.subtitle)}
` : ''}
+
+
+ ${escapeHtml(chName)}
+ ✕
+
+
+
+
+ Alle
+ |
+ Keine
+
+
+
+ ${itemsHtml}
+
+
+ `;
+ }).join('');
+ }
+
+ async function addCookbookRecipeToSelection(recipeId) {
+ recipeId = parseInt(recipeId, 10);
+ if (!recipeId) return;
+
+ if (selectedCookbookRecipes.some(r => r.id === recipeId)) {
+ showToast('Rezept bereits in der Auswahl');
+ return;
+ }
+
+ try {
+ const res = await fetch(`/api/recipes/${recipeId}`);
+ if (!res.ok) throw new Error('Rezept nicht gefunden');
+ const recipeData = await res.json();
+ selectedCookbookRecipes.push(recipeData);
+
+ const container = document.getElementById('cookbook-selected-recipes-container');
+ if (container) {
+ container.innerHTML = renderSelectedCookbookRecipeCards();
+ }
+ updateCookbookConfirmBtn();
+ } catch (err) {
+ console.error('Fehler beim Laden des Rezepts:', err);
+ showToast('Fehler beim Laden des Rezepts');
+ }
+ }
+
+ function removeCookbookRecipeFromSelection(recipeId) {
+ selectedCookbookRecipes = selectedCookbookRecipes.filter(r => r.id !== recipeId);
+ const container = document.getElementById('cookbook-selected-recipes-container');
+ if (container) {
+ container.innerHTML = renderSelectedCookbookRecipeCards();
+ }
+ updateCookbookConfirmBtn();
+ }
+
+ function toggleCookbookRecipeChecks(recipeId, val) {
+ const cbs = document.querySelectorAll(`.cookbook-ing-checkbox[data-recipe-id="${recipeId}"]`);
+ cbs.forEach(cb => cb.checked = val);
+ updateCookbookConfirmBtn();
+ }
+
+ function onSelectCookbookDropdown(recipeId) {
+ if (!recipeId) return;
+ addCookbookRecipeToSelection(recipeId);
+ const sel = document.getElementById('cookbook-recipe-select');
+ if (sel) sel.value = '';
+ }
+
+ function onFilterCookbookSearch(query) {
+ const resultsDiv = document.getElementById('cookbook-search-dropdown-results');
+ if (!resultsDiv || !allCookbookRecipes) return;
+
+ const q = (query || '').toLowerCase().trim();
+ if (!q) {
+ resultsDiv.style.display = 'none';
+ resultsDiv.innerHTML = '';
+ return;
+ }
+
+ const matches = allCookbookRecipes.filter(r => {
+ const titleMatch = (r.title || '').toLowerCase().includes(q);
+ const subMatch = (r.subtitle || '').toLowerCase().includes(q);
+ const ingMatch = (r.ingredients || '').toLowerCase().includes(q);
+ return titleMatch || subMatch || ingMatch;
+ }).slice(0, 10);
+
+ if (matches.length === 0) {
+ resultsDiv.innerHTML = `Kein passendes Rezept gefunden
`;
+ resultsDiv.style.display = 'block';
+ return;
+ }
+
+ resultsDiv.innerHTML = matches.map(r => {
+ const chName = CHAPTER_NAMES[r.chapter_number] || `Kapitel ${r.chapter_number || 1}`;
+ return `
+
+
+ ${escapeHtml(r.title)}
+ ${r.subtitle ? `${escapeHtml(r.subtitle)} ` : ''}
+
+
${escapeHtml(chName)}
+
+ `;
+ }).join('');
+ resultsDiv.style.display = 'block';
+ }
+
+ function selectSearchRecipeMatch(recipeId) {
+ addCookbookRecipeToSelection(recipeId);
+ const searchInput = document.getElementById('cookbook-search-input');
+ if (searchInput) searchInput.value = '';
+ const resultsDiv = document.getElementById('cookbook-search-dropdown-results');
+ if (resultsDiv) {
+ resultsDiv.style.display = 'none';
+ resultsDiv.innerHTML = '';
+ }
+ }
+
+ function updateCookbookConfirmBtn() {
+ const checkedBoxes = Array.from(document.querySelectorAll('.cookbook-ing-checkbox:checked'));
+ const btn = document.getElementById('btn-confirm-import-cookbook');
+ if (btn) {
+ btn.disabled = checkedBoxes.length === 0;
+ btn.textContent = `Ausgewählte übernehmen (${checkedBoxes.length})`;
+ }
+ }
+
+ async function confirmImportCookbookIngredients() {
+ const cbs = Array.from(document.querySelectorAll('.cookbook-ing-checkbox:checked'));
+ if (cbs.length === 0) return;
+
+ const confirmBtn = document.getElementById('btn-confirm-import-cookbook');
+ if (confirmBtn) {
+ confirmBtn.disabled = true;
+ confirmBtn.innerHTML = ' Wird übernommen... ';
+ }
+
+ const itemsToAdd = cbs.map(cb => ({
+ item_name: cb.dataset.name,
+ amount: cb.dataset.amount || '',
+ unit: cb.dataset.unit || '',
+ source: cb.dataset.source || '',
+ category: null,
+ note: ''
+ }));
+
+ try {
+ const res = await fetch('/api/shopping-list/bulk', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ items: itemsToAdd })
+ });
+
+ if (res.ok) {
+ const d = await res.json();
+ if (d.items && Array.isArray(d.items)) {
+ allItems = d.items;
+ } else {
+ await refreshDataFromServer();
+ }
+ saveLocalCache(allItems);
+ closeModal('modal-import-cookbook');
+ renderCategoryFilterChips();
+ renderShoppingList();
+ showToast(`✓ ${itemsToAdd.length} Zutaten übernommen (Mengen zusammengeführt)`);
+ } else {
+ showToast('Fehler beim Übernehmen');
+ if (confirmBtn) {
+ confirmBtn.disabled = false;
+ confirmBtn.textContent = 'Ausgewählte übernehmen';
+ }
+ }
+ } catch (err) {
+ console.error('Fehler beim Importieren aus dem Kochbuch:', err);
+ showToast('Fehler beim Übernehmen');
+ if (confirmBtn) {
+ confirmBtn.disabled = false;
+ confirmBtn.textContent = 'Ausgewählte übernehmen';
+ }
+ }
+ }
+
// ==========================================
// ==========================================
// MODAL: SUPERMARKT-BEREICHE VERWALTUNG
@@ -2108,6 +2536,16 @@
renderCategoryFilterChips();
renderShoppingList();
initWebSocket();
+
+ // Prüfen, ob ein Rezept per URL-Parameter übergeben wurde
+ const urlParams = new URLSearchParams(window.location.search);
+ const impRecipeId = urlParams.get('import_recipe_id');
+ if (impRecipeId) {
+ window.history.replaceState({}, document.title, window.location.pathname);
+ setTimeout(() => {
+ openCookbookImportModal(parseInt(impRecipeId, 10));
+ }, 120);
+ }
});