diff --git a/assets/css/style.css b/assets/css/style.css index b2b2dba..352b7d8 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -2120,6 +2120,11 @@ body { z-index: 250; } +/* Raise modal above cooking overlay when in cooking mode */ +.cooking-mode-active #modal-overlay { + z-index: 600; +} + .modal-content { background: var(--bg-card); border-radius: var(--radius) var(--radius) 0 0; @@ -2929,6 +2934,220 @@ body { line-height: 1.3; } +/* ===== SHOPPING SECTION (REPARTO) HEADERS ===== */ +.shopping-section-divider { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 2px 4px; + font-size: 0.8rem; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + border-bottom: 1px solid var(--border); + margin-top: 4px; +} + +.shopping-section-divider:first-child { + margin-top: 0; + padding-top: 2px; + border-bottom: none; +} + +.shopping-section-divider span.sec-icon { + font-size: 1rem; +} + +/* ===== COOKING MODE ===== */ +.cooking-overlay { + position: fixed; + inset: 0; + background: #0a0a0a; + z-index: 500; + display: flex; + flex-direction: column; + color: #fff; + overflow: hidden; + touch-action: pan-y; +} + +.cooking-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 16px; + background: rgba(255,255,255,0.05); + border-bottom: 1px solid rgba(255,255,255,0.10); + flex-shrink: 0; + min-height: 54px; +} + +.cooking-title { + flex: 1; + text-align: center; + font-size: 0.95rem; + font-weight: 600; + color: rgba(255,255,255,0.8); + margin: 0 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.cooking-close-btn, +.cooking-tts-btn { + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.15); + color: #fff; + border-radius: 50%; + width: 38px; + height: 38px; + font-size: 1rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.2s; + flex-shrink: 0; +} + +.cooking-close-btn:active, +.cooking-tts-btn:active { + background: rgba(255,255,255,0.2); +} + +.cooking-body { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px 20px; + gap: 20px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.cooking-step-num { + font-size: 0.85rem; + font-weight: 700; + color: rgba(255,255,255,0.4); + letter-spacing: 0.1em; + text-align: center; + flex-shrink: 0; +} + +.cooking-step-text { + font-size: clamp(1.4rem, 5vw, 2.2rem); + line-height: 1.5; + font-weight: 500; + color: #fff; + text-align: center; + max-width: 560px; + width: 100%; +} + +.cooking-step-ings { + width: 100%; + max-width: 480px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.cooking-ing-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + background: rgba(255,255,255,0.07); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 10px; + padding: 10px 14px; +} + +.cooking-ing-name { + font-size: 0.95rem; + color: rgba(255,255,255,0.9); + flex: 1; + min-width: 0; +} + +.cooking-use-btn { + flex-shrink: 0; + background: #16a34a; + border: none; + color: #fff; + border-radius: 8px; + padding: 6px 14px; + font-size: 0.82rem; + font-weight: 700; + cursor: pointer; + transition: background 0.2s; + white-space: nowrap; +} + +.cooking-use-btn:active { + background: #15803d; +} + +.cooking-use-btn.btn-used { + background: #374151; + cursor: default; +} + +.cooking-nav { + display: flex; + gap: 12px; + padding: 14px 16px; + background: rgba(255,255,255,0.04); + border-top: 1px solid rgba(255,255,255,0.08); + flex-shrink: 0; +} + +.cooking-nav-btn { + flex: 1; + padding: 14px 10px; + border-radius: 10px; + font-size: 1rem; + font-weight: 700; + border: 1.5px solid rgba(255,255,255,0.2); + background: rgba(255,255,255,0.08); + color: #fff; + cursor: pointer; + transition: background 0.2s; + text-align: center; +} + +.cooking-nav-btn:disabled { + opacity: 0.3; + cursor: default; +} + +.cooking-next-btn { + background: rgba(22, 163, 74, 0.35); + border-color: rgba(22, 163, 74, 0.6); +} + +.cooking-next-btn.is-finish { + background: rgba(22, 163, 74, 0.6); +} + +.cooking-nav-btn:not(:disabled):active { + background: rgba(255,255,255,0.2); +} + +/* Cooking button in recipe dialog */ +.btn-cooking { + background: linear-gradient(135deg, #1e3a5f, #2d5016); + color: #fff; +} + +.btn-cooking:hover, .btn-cooking:active { + opacity: 0.9; +} + /* ===== LOG PAGE ===== */ .log-list { display: flex; diff --git a/assets/js/app.js b/assets/js/app.js index c14c623..6a83419 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -75,6 +75,34 @@ const CATEGORY_LOCATION = { 'cereali': 'dispensa', 'igiene': 'altro', 'pulizia': 'altro', 'altro': 'dispensa' }; +// Shopping section (reparto) map β€” groups categories into grocery departments +const SHOPPING_SECTIONS = [ + { key: 'frutta_verdura', icon: 'πŸ₯¬', label: 'Frutta & Verdura', cats: new Set(['frutta','verdura']) }, + { key: 'carne_pesce', icon: 'πŸ₯©', label: 'Carne & Pesce', cats: new Set(['carne','pesce']) }, + { key: 'latticini', icon: 'πŸ₯›', label: 'Latticini & Fresco', cats: new Set(['latticini']) }, + { key: 'pane_dolci', icon: '🍞', label: 'Pane & Dolci', cats: new Set(['pane','snack','cereali']) }, + { key: 'pasta', icon: '🍝', label: 'Pasta & Cereali', cats: new Set(['pasta']) }, + { key: 'conserve', icon: 'πŸ₯«', label: 'Conserve & Salse', cats: new Set(['conserve','condimenti']) }, + { key: 'surgelati', icon: '❄️', label: 'Surgelati', cats: new Set(['surgelati']) }, + { key: 'bevande', icon: 'πŸ₯€', label: 'Bevande', cats: new Set(['bevande']) }, + { key: 'pulizia_igiene', icon: '🧴', label: 'Pulizia & Igiene', cats: new Set(['igiene','pulizia']) }, + { key: 'altro', icon: 'πŸ“¦', label: 'Altro', cats: new Set(['altro']) }, +]; + +function getItemSection(name) { + const cat = guessCategoryFromName(name) || 'altro'; + for (const s of SHOPPING_SECTIONS) { if (s.cats.has(cat)) return s; } + return SHOPPING_SECTIONS[SHOPPING_SECTIONS.length - 1]; +} + +const URGENCY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 }; +const URGENCY_BG = { + critical: 'rgba(194,65,12,0.14)', + high: 'rgba(234,88,12,0.09)', + medium: 'rgba(245,158,11,0.07)', + low: 'rgba(34,197,94,0.05)', +}; + // Map Open Food Facts categories to local categories function mapToLocalCategory(ofCategory, productName) { if (!ofCategory) { @@ -4404,11 +4432,40 @@ function renderSmartShopping() { low: { color: '#22c55e', bg: 'rgba(34,197,94,0.08)', icon: '🟒', label: 'Previsione' }, }; - container.innerHTML = items.map((item, idx) => { - const u = urgencyConfig[item.urgency] || urgencyConfig.low; - const catIcon = CATEGORY_ICONS[mapToLocalCategory(item.category, item.name)] || 'πŸ“¦'; - const checked = !item.on_bring ? 'checked' : ''; - const globalIdx = smartShoppingItems.indexOf(item); + // Group by section + const smartSectionMap = new Map(); + items.forEach(item => { + const sec = getItemSection(item.name); + if (!smartSectionMap.has(sec.key)) smartSectionMap.set(sec.key, { sec, items: [] }); + smartSectionMap.get(sec.key).items.push(item); + }); + + let smartHtml = ''; + for (const secDef of SHOPPING_SECTIONS) { + const group = smartSectionMap.get(secDef.key); + if (!group) continue; + smartHtml += `
${secDef.icon}${secDef.label}
`; + for (const item of group.items) { + smartHtml += renderSmartItem(item, items); + } + } + container.innerHTML = smartHtml; + + // Show/hide add button based on checkable items + const hasCheckable = items.some(i => !i.on_bring); + actionsEl.style.display = hasCheckable ? 'block' : 'none'; +} + +function renderSmartItem(item) { + const urgencyConfig = { + critical: { color: '#ef4444', bg: 'rgba(239,68,68,0.08)', icon: 'πŸ”΄', label: 'Urgente' }, + high: { color: '#f97316', bg: 'rgba(249,115,22,0.08)', icon: '🟠', label: 'Presto' }, + medium: { color: '#eab308', bg: 'rgba(234,179,8,0.08)', icon: '🟑', label: 'Pianifica' }, + low: { color: '#22c55e', bg: 'rgba(34,197,94,0.08)', icon: '🟒', label: 'Previsione' }, + }; + const u = urgencyConfig[item.urgency] || urgencyConfig.low; + const catIcon = CATEGORY_ICONS[mapToLocalCategory(item.category, item.name)] || 'πŸ“¦'; + const globalIdx = smartShoppingItems.indexOf(item); // Stock bar const pct = Math.min(100, Math.max(0, item.pct_left)); @@ -4448,7 +4505,7 @@ function renderSmartShopping() { return `
- ${!item.on_bring ? `` : ''} + ${!item.on_bring ? `` : ''} ${catIcon}
${escapeHtml(item.name)}${item.brand ? ` ${escapeHtml(item.brand)}` : ''}
@@ -4466,11 +4523,6 @@ function renderSmartShopping() {
`; - }).join(''); - - // Show/hide add button based on checkable items - const hasCheckable = items.some(i => !i.on_bring); - actionsEl.style.display = hasCheckable ? 'block' : 'none'; } async function addSmartToBring() { @@ -4581,8 +4633,11 @@ async function loadShoppingList() { renderShoppingItems(); currentEl.style.display = 'block'; - // Load smart shopping predictions (auto-add critical after loading) - loadSmartShopping().then(() => autoAddCriticalItems()); + // Load smart shopping predictions, then re-render to show badges + auto-add critical + loadSmartShopping().then(() => { + autoAddCriticalItems(); + renderShoppingItems(); // re-render to apply urgency badges from smart data + }); // Show tabs once data is ready updateShoppingTabCounts(); @@ -4598,15 +4653,6 @@ async function renderShoppingItems() { const container = document.getElementById('shopping-items'); const countEl = document.getElementById('shopping-count'); - // Sort shoppingItems in-place by use_count (cross-reference smartShoppingItems), most frequent first - shoppingItems.sort((a, b) => { - const smartA = smartShoppingItems.find(s => s.name.toLowerCase() === a.name.toLowerCase()); - const smartB = smartShoppingItems.find(s => s.name.toLowerCase() === b.name.toLowerCase()); - const freqA = smartA ? smartA.use_count : 0; - const freqB = smartB ? smartB.use_count : 0; - return freqB - freqA; - }); - countEl.textContent = shoppingItems.length; // Update tab count too const tabCount = document.getElementById('tab-count-acquisto'); @@ -4634,110 +4680,144 @@ async function renderShoppingItems() { } } catch (e) { /* ignore */ } } - - container.innerHTML = shoppingItems.map((item, idx) => { - const catIcon = CATEGORY_ICONS[guessCategoryFromName(item.name)] || 'πŸ›’'; - const priceKey = item.name.toLowerCase(); - const priceData = shoppingPrices[priceKey]; - // Cross-reference with smart shopping for urgency + frequency - const smartData = smartShoppingItems.find(s => s.name.toLowerCase() === item.name.toLowerCase()); - const localTags = getShoppingTags(item.name); - // Urgency/frequency badges - let urgencyBadge = ''; - if (smartData) { - const urgencyMap = { - critical: { icon: 'πŸ”΄', label: 'Urgente', cls: 'badge-critical' }, - high: { icon: '🟠', label: 'Presto', cls: 'badge-high' }, - medium: { icon: '🟑', label: 'Presto', cls: 'badge-medium' }, - low: { icon: '🟒', label: 'Ok', cls: 'badge-low' }, - }; - const u = urgencyMap[smartData.urgency]; - if (u) urgencyBadge = `${u.icon} ${u.label}`; - } + // Build section groups, sorted by urgency weight within each section + const TAG_LABELS = { urgente: 'πŸ”΄ Urgente', prio: '⭐ PrioritΓ ', check: 'βœ… Verificare' }; + const urgencyMap = { + critical: { icon: 'πŸ”΄', label: 'Urgente', cls: 'badge-critical' }, + high: { icon: '🟠', label: 'Presto', cls: 'badge-high' }, + medium: { icon: '🟑', label: 'Medio', cls: 'badge-medium' }, + low: { icon: '🟒', label: 'Ok', cls: 'badge-low' }, + }; - let freqBadge = ''; - if (smartData && smartData.use_count >= 8) freqBadge = `πŸ“ˆ ${smartData.use_count}x`; - else if (smartData && smartData.use_count >= 4) freqBadge = `πŸ“Š ${smartData.use_count}x`; - else if (smartData && smartData.use_count >= 2) freqBadge = `πŸ“‰ ${smartData.use_count}x`; + // Map each item to its section + urgency + const enriched = shoppingItems.map((item, idx) => { + const smartData = smartShoppingItems.find(sd => sd.name.toLowerCase() === item.name.toLowerCase()); + const urgency = smartData?.urgency || null; + const sec = getItemSection(item.name); + return { item, idx, smartData, urgency, sec }; + }); - // Local tags - const TAG_LABELS = { urgente: 'πŸ”΄ Urgente', prio: '⭐ PrioritΓ ', check: 'βœ… Verificare' }; - const localTagHtml = localTags.map(t => - `${TAG_LABELS[t] || t} βœ•` - ).join(''); + // Group by section key, preserving SHOPPING_SECTIONS order + const sectionMap = new Map(); + for (const e of enriched) { + const key = e.sec.key; + if (!sectionMap.has(key)) sectionMap.set(key, { sec: e.sec, items: [] }); + sectionMap.get(key).items.push(e); + } - // Tag add button - const tagMenu = `
- ${Object.entries(TAG_LABELS).map(([k, v]) => - `` - ).join('')} -
`; - - let detailHtml = ''; - let priceTag = ''; - let spesaBar = ''; - if (hasSpesa) { - if (priceData && priceData.loading) { - detailHtml = `
πŸ” Cerco...
`; - } else if (priceData && priceData.product) { - const p = priceData.product; - const promoHtml = p.promo - ? `${escapeHtml(p.promo.label)} -${Math.round(p.promo.discountPerc)}%` - : ''; - const est = estimateItemPrice(p, item.specification || priceData.spec || ''); - if (est) { - priceTag = `
~€${est.estimated.toFixed(2)}
`; - } else { - priceTag = `
€${p.price.toFixed(2)}
`; - } - detailHtml = `
- ${escapeHtml(p.name)} - ${escapeHtml(p.packageDescr)}${est ? ' Β· ' + escapeHtml(String(p.priceUm || '')) + '/kg' : ''} - ${promoHtml} -
`; - spesaBar = `
- - πŸ”— Apri -
`; - } else if (priceData && priceData.searched && !priceData.product) { - detailHtml = `
Non trovato
`; - spesaBar = `
- -
`; - } else { - spesaBar = `
- -
`; + // Sort items within each section: by urgency weight desc, then by use_count desc + for (const [, group] of sectionMap) { + group.items.sort((a, b) => { + const wa = URGENCY_WEIGHT[a.urgency] || 0; + const wb = URGENCY_WEIGHT[b.urgency] || 0; + if (wb !== wa) return wb - wa; + return (b.smartData?.use_count || 0) - (a.smartData?.use_count || 0); + }); + } + + // Render sections in canonical order + let html = ''; + for (const secDef of SHOPPING_SECTIONS) { + const group = sectionMap.get(secDef.key); + if (!group) continue; + + html += `
${secDef.icon}${secDef.label}
`; + + for (const { item, idx, smartData, urgency } of group.items) { + const catIcon = CATEGORY_ICONS[guessCategoryFromName(item.name)] || 'πŸ›’'; + const priceKey = item.name.toLowerCase(); + const priceData = shoppingPrices[priceKey]; + const bgStyle = urgency && URGENCY_BG[urgency] ? ` style="background:${URGENCY_BG[urgency]}"` : ''; + const localTags = getShoppingTags(item.name); + + // Urgency badge + let urgencyBadge = ''; + if (urgency && urgencyMap[urgency]) { + const u = urgencyMap[urgency]; + urgencyBadge = `${u.icon} ${u.label}`; } - } - - return ` -
- ${catIcon} -
-
-
-
- ${escapeHtml(item.name)} - πŸ“· + + // Frequency badge + let freqBadge = ''; + if (smartData && smartData.use_count >= 8) freqBadge = `πŸ“ˆ ${smartData.use_count}x`; + else if (smartData && smartData.use_count >= 4) freqBadge = `πŸ“Š ${smartData.use_count}x`; + else if (smartData && smartData.use_count >= 2) freqBadge = `πŸ“‰ ${smartData.use_count}x`; + + const localTagHtml = localTags.map(t => + `${TAG_LABELS[t] || t} βœ•` + ).join(''); + + const tagMenu = `
+ ${Object.entries(TAG_LABELS).map(([k, v]) => + `` + ).join('')} +
`; + + let detailHtml = ''; + let priceTag = ''; + let spesaBar = ''; + if (hasSpesa) { + if (priceData && priceData.loading) { + detailHtml = `
πŸ” Cerco...
`; + } else if (priceData && priceData.product) { + const p = priceData.product; + const promoHtml = p.promo + ? `${escapeHtml(p.promo.label)} -${Math.round(p.promo.discountPerc)}%` + : ''; + const est = estimateItemPrice(p, item.specification || priceData.spec || ''); + priceTag = est + ? `
~€${est.estimated.toFixed(2)}
` + : `
€${p.price.toFixed(2)}
`; + detailHtml = `
+ ${escapeHtml(p.name)} + ${escapeHtml(p.packageDescr)}${est ? ' Β· ' + escapeHtml(String(p.priceUm || '')) + '/kg' : ''} + ${promoHtml} +
`; + spesaBar = `
+ + πŸ”— Apri +
`; + } else if (priceData && priceData.searched && !priceData.product) { + detailHtml = `
Non trovato
`; + spesaBar = `
+ +
`; + } else { + spesaBar = `
+ +
`; + } + } + + html += ` +
+ ${catIcon} +
+
+
+
+ ${escapeHtml(item.name)} + πŸ“· +
+ ${item.specification ? `
${escapeHtml(item.specification)}
` : ''} + ${(urgencyBadge || freqBadge || localTagHtml) ? `
${urgencyBadge}${freqBadge}${localTagHtml}
` : ''} + ${detailHtml} +
+
+ ${priceTag} + +
- ${item.specification ? `
${escapeHtml(item.specification)}
` : ''} - ${(urgencyBadge || freqBadge || localTagHtml) ? `
${urgencyBadge}${freqBadge}${localTagHtml}
` : ''} - ${detailHtml} -
-
- ${priceTag} - -
+ ${spesaBar} +
- ${spesaBar} - -
-
`; - }).join(''); - +
`; + } + } + + container.innerHTML = html; updateSpesaTotal(); } @@ -5867,6 +5947,133 @@ function renderRecipe(r) { document.getElementById('recipe-content').innerHTML = html; } +// ===== COOKING MODE ===== +let _cookingRecipe = null; +let _cookingStep = 0; +let _cookingTTS = true; + +function startCookingMode() { + const recipe = _cachedRecipe && _cachedRecipe.recipe ? _cachedRecipe.recipe : null; + if (!recipe || !(recipe.steps || []).length) { + showToast('Nessun procedimento disponibile', 'info'); + return; + } + _cookingRecipe = JSON.parse(JSON.stringify(recipe)); // deep copy so we can track .used + _cookingStep = 0; + _cookingTTS = 'speechSynthesis' in window; + document.getElementById('cooking-title').textContent = _cookingRecipe.title || ''; + document.getElementById('cooking-tts-btn').textContent = _cookingTTS ? 'πŸ”Š' : 'πŸ”‡'; + document.getElementById('cooking-overlay').style.display = 'flex'; + document.body.classList.add('cooking-mode-active'); + try { screen.orientation?.lock('portrait'); } catch (_) { /* ignore */ } + // Preload voices for TTS + if (_cookingTTS) window.speechSynthesis.getVoices(); + renderCookingStep(); +} + +function closeCookingMode() { + document.getElementById('cooking-overlay').style.display = 'none'; + document.body.classList.remove('cooking-mode-active'); + if ('speechSynthesis' in window) window.speechSynthesis.cancel(); + try { screen.orientation?.unlock(); } catch (_) { /* ignore */ } +} + +function renderCookingStep() { + if (!_cookingRecipe) return; + const steps = _cookingRecipe.steps || []; + const step = steps[_cookingStep] || ''; + const cleanStep = step.replace(/^Passo\s*\d+\s*[:.]\s*/i, ''); + const total = steps.length; + + document.getElementById('cooking-step-num').textContent = `${_cookingStep + 1} / ${total}`; + document.getElementById('cooking-step-text').textContent = cleanStep; + + // Find pantry ingredients that appear in this step's text and haven't been used yet + const stepLower = cleanStep.toLowerCase(); + const ings = (_cookingRecipe.ingredients || []) + .map((ing, idx) => ({ ...ing, _idx: idx })) + .filter(ing => ing.from_pantry && ing.product_id && ing.used !== true) + .filter(ing => { + const name = (ing.name || '').toLowerCase(); + return name.split(' ').some(word => word.length > 2 && stepLower.includes(word)); + }); + + const ingsEl = document.getElementById('cooking-step-ings'); + if (ings.length > 0) { + ingsEl.innerHTML = ings.map(ing => { + const loc = (ing.location || 'dispensa').replace(/'/g, "\\'"); + const qtyNum = ing.qty_number || 0; + return `
+ πŸ“¦ ${escapeHtml(ing.name)}: ${escapeHtml(ing.qty)} + +
`; + }).join(''); + ingsEl.style.display = 'flex'; + } else { + ingsEl.innerHTML = ''; + ingsEl.style.display = 'none'; + } + + // Navigation button states + const prevBtn = document.getElementById('cooking-prev'); + const nextBtn = document.getElementById('cooking-next'); + prevBtn.disabled = _cookingStep === 0; + nextBtn.textContent = _cookingStep === total - 1 ? 'βœ… Fine' : 'Successivo β–Ά'; + + // Speak step + if (_cookingTTS) speakCookingStep(cleanStep); +} + +function speakCookingStep(text) { + if (!('speechSynthesis' in window)) return; + window.speechSynthesis.cancel(); + const utt = new SpeechSynthesisUtterance(text); + utt.lang = 'it-IT'; + utt.rate = 0.9; + utt.pitch = 1.0; + const voices = window.speechSynthesis.getVoices(); + const itVoice = voices.find(v => v.lang.startsWith('it')); + if (itVoice) utt.voice = itVoice; + window.speechSynthesis.speak(utt); +} + +function toggleCookingTTS() { + _cookingTTS = !_cookingTTS; + const btn = document.getElementById('cooking-tts-btn'); + btn.textContent = _cookingTTS ? 'πŸ”Š' : 'πŸ”‡'; + if (_cookingTTS) { + const steps = _cookingRecipe?.steps || []; + const text = (steps[_cookingStep] || '').replace(/^Passo\s*\d+\s*[:.]\s*/i, ''); + speakCookingStep(text); + } else { + window.speechSynthesis?.cancel(); + } +} + +function navigateCookingStep(delta) { + if (!_cookingRecipe) return; + const total = (_cookingRecipe.steps || []).length; + const next = _cookingStep + delta; + if (next < 0) return; + if (next >= total) { + closeCookingMode(); + return; + } + _cookingStep = next; + renderCookingStep(); +} + +function cookingUseIngredient(idx, productId, location, qtyNumber, btn) { + // Reuse the same modal used in the recipe dialog + useRecipeIngredient(idx, productId, location, qtyNumber, btn); + // Mark ingredient as used so it's hidden from further steps + if (_cookingRecipe && _cookingRecipe.ingredients && _cookingRecipe.ingredients[idx]) { + _cookingRecipe.ingredients[idx].used = true; + } + setTimeout(() => renderCookingStep(), 400); +} +// ===== END COOKING MODE ===== + function updateRecipeMealTitle() { const meal = getSelectedMealType(); document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; diff --git a/data/dispensa.db b/data/dispensa.db index 5d32e04..b2730b9 100644 Binary files a/data/dispensa.db and b/data/dispensa.db differ diff --git a/index.html b/index.html index 412dadd..754da28 100644 --- a/index.html +++ b/index.html @@ -928,6 +928,9 @@
+ + +