Add cookbook recipe import to shopping list with deduplication and quantity merging
This commit is contained in:
@@ -24,6 +24,9 @@
|
||||
<span id="cookmode-nav-badge" class="cb-badge-pill">Aus</span>
|
||||
</button>
|
||||
{% if not is_shared %}
|
||||
<a href="/shopping-list?import_recipe_id={{ recipe.id }}" class="cb-btn cb-btn-outline" title="Zutaten auf die Einkaufsliste übernehmen">
|
||||
<span>🛒</span> <span>Auf Einkaufsliste</span>
|
||||
</a>
|
||||
<a href="/cookbook" class="cb-btn cb-btn-outline">
|
||||
<span>📖</span> <span>Alle Rezepte</span>
|
||||
</a>
|
||||
@@ -65,6 +68,9 @@
|
||||
</button>
|
||||
<hr class="cb-mobile-divider">
|
||||
{% if not is_shared %}
|
||||
<a href="/shopping-list?import_recipe_id={{ recipe.id }}" class="cb-mobile-item">
|
||||
<span>🛒</span> <span>Auf Einkaufsliste</span>
|
||||
</a>
|
||||
<button type="button" onclick="openShareModal()" class="cb-mobile-item">
|
||||
<span>🔗</span> <span>Rezept teilen</span>
|
||||
</button>
|
||||
|
||||
@@ -541,6 +541,9 @@
|
||||
<button type="button" onclick="openMealPlanImportModal()" class="cb-nav-btn" title="Zutaten aus Wochenplan holen">
|
||||
<span>📅</span> <span>Aus Wochenplan holen</span>
|
||||
</button>
|
||||
<button type="button" onclick="openCookbookImportModal()" class="cb-nav-btn" title="Zutaten aus einem Kochbuch-Rezept holen">
|
||||
<span>📖</span> <span>Aus Kochbuch holen</span>
|
||||
</button>
|
||||
<button type="button" onclick="deleteCheckedItems()" class="cb-nav-btn" title="Erledigte Artikel löschen">
|
||||
<span>🧹</span> <span>Erledigte löschen</span>
|
||||
</button>
|
||||
@@ -573,6 +576,9 @@
|
||||
<button type="button" onclick="handleMobileMenuAction(openMealPlanImportModal)" class="cb-mobile-item">
|
||||
<span>📅</span> <span>Aus Wochenplan holen</span>
|
||||
</button>
|
||||
<button type="button" onclick="handleMobileMenuAction(openCookbookImportModal)" class="cb-mobile-item">
|
||||
<span>📖</span> <span>Aus Kochbuch holen</span>
|
||||
</button>
|
||||
<button type="button" onclick="handleMobileMenuAction(deleteCheckedItems)" class="cb-mobile-item">
|
||||
<span>🧹</span> <span>Erledigte löschen</span>
|
||||
</button>
|
||||
@@ -616,10 +622,15 @@
|
||||
<div class="shop-empty-state" id="empty-state" style="display: none;">
|
||||
<div class="shop-empty-icon">🎉</div>
|
||||
<h2 class="shop-empty-title">Alles eingekauft!</h2>
|
||||
<p class="shop-empty-desc">Deine Einkaufsliste ist leer. Tippe unten rechts auf das <strong>+</strong>, um einen neuen Eintrag zu notieren, oder übernimm Zutaten direkt aus dem Wochenplan.</p>
|
||||
<button type="button" class="cb-btn cb-btn-green" onclick="openMealPlanImportModal()">
|
||||
<span>📅</span> <span>Aus Wochenplan importieren</span>
|
||||
</button>
|
||||
<p class="shop-empty-desc">Deine Einkaufsliste ist leer. Tippe unten rechts auf das <strong>+</strong>, um einen neuen Eintrag zu notieren, oder übernimm Zutaten direkt aus dem Wochenplan oder Kochbuch.</p>
|
||||
<div style="display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
|
||||
<button type="button" class="cb-btn cb-btn-green" onclick="openMealPlanImportModal()">
|
||||
<span>📅</span> <span>Aus Wochenplan holen</span>
|
||||
</button>
|
||||
<button type="button" class="cb-btn cb-btn-creme" onclick="openCookbookImportModal()" style="border: 1.5px solid var(--shop-green); color: var(--shop-green); font-weight: 700;">
|
||||
<span>📖</span> <span>Aus Kochbuch holen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -816,6 +827,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: ZUTATEN AUS KOCHBUCH HOLEN -->
|
||||
<!-- ========================================== -->
|
||||
<div id="modal-import-cookbook" class="cb-modal-backdrop no-print" onclick="onBackdropClick(event, 'modal-import-cookbook')">
|
||||
<div class="cb-modal-card" style="max-width: 600px;">
|
||||
<div class="cb-modal-header">
|
||||
<h3 class="cb-modal-title"><span>📖</span> Zutaten aus Kochbuch holen</h3>
|
||||
<button type="button" class="cb-modal-close" onclick="closeModal('modal-import-cookbook')" title="Schließen">×</button>
|
||||
</div>
|
||||
<div class="cb-modal-body">
|
||||
<div id="cookbook-import-body" style="min-height: 120px;">
|
||||
<!-- Dynamischer Inhalt -->
|
||||
</div>
|
||||
<div class="modal-btn-row">
|
||||
<button type="button" class="cb-btn cb-btn-secondary" onclick="closeModal('modal-import-cookbook')">
|
||||
Schließen
|
||||
</button>
|
||||
<button type="button" class="cb-btn cb-btn-green" id="btn-confirm-import-cookbook" onclick="confirmImportCookbookIngredients()" disabled>
|
||||
Ausgewählte übernehmen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: SCHNELL-REGALWAHL (1-TAP) -->
|
||||
<!-- ========================================== -->
|
||||
@@ -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 = `
|
||||
<div style="text-align: center; padding: 35px 20px; color: #555;">
|
||||
<div class="shop-spinner shop-spinner-green" style="margin-bottom: 12px;"></div>
|
||||
<div style="font-weight: 600; font-size: 0.95rem; color: var(--shop-green);">Rezepte werden geladen...</div>
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<div style="text-align: center; padding: 25px; color: #b02a37;">
|
||||
Fehler beim Laden der Rezepte. Bitte stelle sicher, dass du online bist.
|
||||
</div>
|
||||
`;
|
||||
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 = '<option value="">-- Rezept auswählen... --</option>';
|
||||
sortedChapters.forEach(ch => {
|
||||
const chName = CHAPTER_NAMES[ch] || `Kapitel ${ch}`;
|
||||
optionsHtml += `<optgroup label="Kapitel ${ch}: ${escapeAttr(chName)}">`;
|
||||
recipesByChapter[ch].forEach(r => {
|
||||
const sub = r.subtitle ? ` (${r.subtitle})` : '';
|
||||
optionsHtml += `<option value="${r.id}">${escapeHtml(r.title)}${escapeHtml(sub)}</option>`;
|
||||
});
|
||||
optionsHtml += `</optgroup>`;
|
||||
});
|
||||
|
||||
body.innerHTML = `
|
||||
<div style="margin-bottom: 14px; background: rgba(30, 77, 59, 0.04); border: 1.5px solid rgba(30, 77, 59, 0.15); border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-wrap: wrap; gap: 4px;">
|
||||
<label style="font-family: var(--font-hand); font-size: 1.15rem; font-weight: 700; color: var(--shop-green); margin: 0;">
|
||||
📖 Rezept aus dem Kochbuch wählen:
|
||||
</label>
|
||||
<span style="font-size: 0.8rem; color: #666;">${allCookbookRecipes.length} Rezepte</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px; position: relative;">
|
||||
<input type="text" id="cookbook-search-input" class="cb-input"
|
||||
placeholder="🔍 Tippen zum Suchen (z. B. Chili, Strudel, Suppe...)"
|
||||
oninput="onFilterCookbookSearch(this.value)"
|
||||
autocomplete="off"
|
||||
style="width: 100%; box-sizing: border-box; padding: 8px 12px; font-size: 0.92rem; border-radius: 6px;">
|
||||
<div id="cookbook-search-dropdown-results" style="display: none; max-height: 180px; overflow-y: auto; background: #fff; border: 1.5px solid var(--shop-green); border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 50;"></div>
|
||||
<select id="cookbook-recipe-select" class="cb-select" onchange="onSelectCookbookDropdown(this.value)" style="width: 100%; box-sizing: border-box; padding: 8px 12px; font-size: 0.92rem; border-radius: 6px;">
|
||||
${optionsHtml}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cookbook-selected-recipes-container" style="max-height: 48vh; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; padding-right: 4px;">
|
||||
${renderSelectedCookbookRecipeCards()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
updateCookbookConfirmBtn();
|
||||
}
|
||||
|
||||
function renderSelectedCookbookRecipeCards() {
|
||||
if (selectedCookbookRecipes.length === 0) {
|
||||
return `
|
||||
<div style="text-align: center; padding: 25px 15px; color: #666; background: rgba(30, 77, 59, 0.02); border: 1.5px dashed rgba(30, 77, 59, 0.2); border-radius: 8px;">
|
||||
<span style="font-size: 2.2rem; display: block; margin-bottom: 6px;">📖</span>
|
||||
<p style="font-size: 1.05rem; margin: 0 0 6px 0; font-weight: 700; color: var(--shop-green);">Noch kein Rezept ausgewählt</p>
|
||||
<p style="font-size: 0.88rem; margin: 0; color: #777; max-width: 440px; margin-inline: auto;">
|
||||
Wähle oben ein Rezept aus, um dessen Zutaten auf deine Einkaufsliste zu setzen. Mengen von bereits vorhandenen Zutaten werden automatisch addiert – ohne Dopplungen!
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 = `<div style="color: #888; font-size: 0.88rem; font-style: italic; padding: 6px 0;">Keine Zutaten für dieses Rezept hinterlegt.</div>`;
|
||||
} else {
|
||||
itemsHtml = ingredients.map(ing => {
|
||||
if (ing.is_header) {
|
||||
return `<div style="font-weight: 700; color: var(--shop-green); font-size: 0.85rem; margin-top: 6px; font-family: var(--font-hand);">${escapeHtml(ing.raw)}</div>`;
|
||||
}
|
||||
|
||||
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 `
|
||||
<label style="display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 0.92rem; cursor: pointer; padding: 4px 6px; border-radius: 4px; background: ${match ? 'rgba(46, 125, 50, 0.06)' : 'transparent'};">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" class="cookbook-ing-checkbox" checked
|
||||
data-recipe-id="${r.id}"
|
||||
data-name="${escapeAttr(cleanName)}"
|
||||
data-amount="${escapeAttr(ing.amount_formatted || ing.amount_str || (ing.amount !== null && ing.amount !== undefined ? String(ing.amount) : ''))}"
|
||||
data-unit="${escapeAttr(ing.unit || '')}"
|
||||
data-source="${escapeAttr(r.title)}"
|
||||
onchange="updateCookbookConfirmBtn()">
|
||||
<span>${amtStr ? `<strong>${escapeHtml(amtStr)}</strong> ` : ''}${escapeHtml(cleanName)}</span>
|
||||
</div>
|
||||
${match ? `
|
||||
<span style="font-size: 0.72rem; background: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; border-radius: 10px; padding: 1px 7px; font-weight: 600; white-space: nowrap;" title="Bereits auf der Einkaufsliste – Menge wird addiert!">
|
||||
➕ wird addiert (${escapeHtml(match.amount ? match.amount + ' ' + (match.unit || '') : 'auf Liste')})
|
||||
</span>
|
||||
` : ''}
|
||||
</label>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="cookbook-recipe-card" data-recipe-id="${r.id}" style="background: rgba(30, 77, 59, 0.05); border: 1.5px solid rgba(30, 77, 59, 0.18); border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; gap: 8px;">
|
||||
<div>
|
||||
<div style="font-family: var(--font-hand); font-size: 1.25rem; font-weight: 700; color: var(--shop-green);">
|
||||
🍳 ${escapeHtml(r.title)}
|
||||
</div>
|
||||
${r.subtitle ? `<div style="font-size: 0.8rem; color: #666; font-style: italic;">${escapeHtml(r.subtitle)}</div>` : ''}
|
||||
</div>
|
||||
<div style="display: flex; gap: 6px; align-items: center;">
|
||||
<span style="font-size: 0.72rem; background: rgba(30, 77, 59, 0.12); color: var(--shop-green); padding: 2px 7px; border-radius: 6px; font-weight: 600;">${escapeHtml(chName)}</span>
|
||||
<button type="button" onclick="removeCookbookRecipeFromSelection(${r.id})" title="Dieses Rezept entfernen" style="background: none; border: none; color: #b02a37; font-size: 1.15rem; cursor: pointer; padding: 0 4px; line-height: 1;">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-bottom: 8px; font-size: 0.82rem;">
|
||||
<button type="button" onclick="toggleCookbookRecipeChecks(${r.id}, true)" style="background: none; border: none; color: var(--shop-green); font-weight: 700; cursor: pointer; text-decoration: underline;">Alle</button>
|
||||
<span style="color: #ccc;">|</span>
|
||||
<button type="button" onclick="toggleCookbookRecipeChecks(${r.id}, false)" style="background: none; border: none; color: #888; font-weight: 600; cursor: pointer; text-decoration: underline;">Keine</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 4px;">
|
||||
${itemsHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).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 = `<div style="padding: 10px; font-size: 0.88rem; color: #888; text-align: center;">Kein passendes Rezept gefunden</div>`;
|
||||
resultsDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = matches.map(r => {
|
||||
const chName = CHAPTER_NAMES[r.chapter_number] || `Kapitel ${r.chapter_number || 1}`;
|
||||
return `
|
||||
<div onclick="selectSearchRecipeMatch(${r.id})" style="padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; transition: background 0.15s ease;" onmouseover="this.style.background='rgba(30,77,59,0.08)'" onmouseout="this.style.background='transparent'">
|
||||
<div>
|
||||
<strong style="color: var(--shop-green);">${escapeHtml(r.title)}</strong>
|
||||
${r.subtitle ? `<span style="font-size: 0.8rem; color: #666; margin-left: 6px;">${escapeHtml(r.subtitle)}</span>` : ''}
|
||||
</div>
|
||||
<span style="font-size: 0.72rem; color: #888;">${escapeHtml(chName)}</span>
|
||||
</div>
|
||||
`;
|
||||
}).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 = '<span class="shop-spinner" style="margin-right: 6px;"></span> <span>Wird übernommen...</span>';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user