From 469aadb8fcb6b41e33d27d4753e34dbcf99f2c8b Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Wed, 11 Mar 2026 13:08:02 +0000 Subject: [PATCH] Aggiunta pagina impostazioni, preview prodotto migliorata, gestione inventario smart e ricette avanzate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Icona ingranaggio nella navbar, salvataggio su localStorage e .env - Preview prodotto più grande dopo scansione barcode - Controllo inventario dopo scan: mostra quantità disponibile in grande - 3 pulsanti contestuali (AGGIUNGI/USA/BUTTA) se prodotto già presente - Funzionalità BUTTA con modale per quantità parziale o totale - Quantità prominenti nella lista inventario - Quantità visibili negli alert scadenza/scaduti in dashboard - Unità di misura modificabile nella modale di modifica inventario - Opzioni ricetta: Pasto Veloce, Poca Fame, Priorità Scadenze, ecc. - Gestione smart quantità ricette (evita rimasugli inutilizzabili) - Elettrodomestici configurabili per suggerimenti ricette - Restrizioni alimentari nel prompt ricette - Endpoint API: save_settings, get_settings --- api/index.php | 151 ++++++++++++++- assets/css/style.css | 330 +++++++++++++++++++++++++++++++++ assets/js/app.js | 431 +++++++++++++++++++++++++++++++++++++++++-- data/dispensa.db | Bin 81920 -> 81920 bytes index.html | 125 ++++++++++++- 5 files changed, 1017 insertions(+), 20 deletions(-) diff --git a/api/index.php b/api/index.php index 3a36e75..661bdb8 100644 --- a/api/index.php +++ b/api/index.php @@ -109,6 +109,14 @@ try { bringSuggestItems($db); break; + case 'save_settings': + saveSettings(); + break; + + case 'get_settings': + getServerSettings(); + break; + default: http_response_code(404); echo json_encode(['error' => 'Unknown action: ' . $action]); @@ -421,6 +429,7 @@ function useFromInventory(PDO $db): void { $quantity = $input['quantity'] ?? 0; $useAll = $input['use_all'] ?? false; $location = $input['location'] ?? 'dispensa'; + $notes = $input['notes'] ?? ''; if (!$productId) { http_response_code(400); @@ -428,6 +437,24 @@ function useFromInventory(PDO $db): void { return; } + // Handle "throw all from all locations" + if ($useAll && $location === '__all__') { + $stmt = $db->prepare("SELECT id, quantity, location FROM inventory WHERE product_id = ? AND quantity > 0"); + $stmt->execute([$productId]); + $allItems = $stmt->fetchAll(); + $totalRemoved = 0; + foreach ($allItems as $item) { + $totalRemoved += $item['quantity']; + $stmt = $db->prepare("DELETE FROM inventory WHERE id = ?"); + $stmt->execute([$item['id']]); + $type = ($notes === 'Buttato') ? 'waste' : 'out'; + $stmt = $db->prepare("INSERT INTO transactions (product_id, type, quantity, location, notes) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$productId, $type, $item['quantity'], $item['location'], $notes]); + } + echo json_encode(['success' => true, 'remaining' => 0, 'removed' => $totalRemoved]); + return; + } + $stmt = $db->prepare("SELECT id, quantity FROM inventory WHERE product_id = ? AND location = ? AND quantity > 0"); $stmt->execute([$productId, $location]); $existing = $stmt->fetch(); @@ -453,8 +480,9 @@ function useFromInventory(PDO $db): void { } // Log transaction - $stmt = $db->prepare("INSERT INTO transactions (product_id, type, quantity, location) VALUES (?, 'out', ?, ?)"); - $stmt->execute([$productId, $quantity, $location]); + $type = ($notes === 'Buttato') ? 'waste' : 'out'; + $stmt = $db->prepare("INSERT INTO transactions (product_id, type, quantity, location, notes) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([$productId, $type, $quantity, $location, $notes]); // Auto-add to Bring! if product is completely finished (no inventory left anywhere) $addedToBring = false; @@ -514,6 +542,13 @@ function updateInventory(PDO $db): void { $stmt = $db->prepare("UPDATE inventory SET " . implode(', ', $fields) . " WHERE id = ?"); $stmt->execute($params); + + // Update unit on the product if provided + if (isset($input['unit']) && isset($input['product_id'])) { + $stmt = $db->prepare("UPDATE products SET unit = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"); + $stmt->execute([$input['unit'], $input['product_id']]); + } + echo json_encode(['success' => true]); } @@ -572,7 +607,7 @@ function getStats(PDO $db): void { // Expiring soonest (next 4 items to expire) $expiring = $db->query(" - SELECT i.*, p.name, p.brand, p.category + SELECT i.*, p.name, p.brand, p.category, p.unit FROM inventory i JOIN products p ON i.product_id = p.id WHERE i.expiry_date IS NOT NULL AND i.expiry_date >= date('now') AND i.quantity > 0 ORDER BY i.expiry_date ASC @@ -581,7 +616,7 @@ function getStats(PDO $db): void { // Expired $expired = $db->query(" - SELECT i.*, p.name, p.brand, p.category + SELECT i.*, p.name, p.brand, p.category, p.unit FROM inventory i JOIN products p ON i.product_id = p.id WHERE i.expiry_date IS NOT NULL AND i.expiry_date < date('now') ORDER BY i.expiry_date ASC @@ -598,6 +633,76 @@ function getStats(PDO $db): void { ]); } +// ===== SETTINGS ===== + +function getServerSettings(): void { + $envFile = __DIR__ . '/../.env'; + $envVars = []; + if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + foreach ($lines as $line) { + if (strpos($line, '#') === 0 || strpos($line, '=') === false) continue; + list($key, $val) = explode('=', $line, 2); + $envVars[trim($key)] = trim($val); + } + } + + // Return masked versions for security + $geminiKey = $envVars['GEMINI_API_KEY'] ?? ''; + $bringEmail = $envVars['BRING_EMAIL'] ?? ''; + $bringPassword = $envVars['BRING_PASSWORD'] ?? ''; + + echo json_encode([ + 'gemini_key' => $geminiKey, + 'gemini_key_set' => !empty($geminiKey), + 'bring_email' => $bringEmail, + 'bring_password_set' => !empty($bringPassword) + ]); +} + +function saveSettings(): void { + $input = json_decode(file_get_contents('php://input'), true); + $envFile = __DIR__ . '/../.env'; + + // Read existing .env content + $envContent = ''; + $envVars = []; + if (file_exists($envFile)) { + $lines = file($envFile, FILE_IGNORE_NEW_LINES); + foreach ($lines as $line) { + if (strpos($line, '#') === 0 || strpos($line, '=') === false) { + continue; + } + list($key, $val) = explode('=', $line, 2); + $envVars[trim($key)] = trim($val); + } + } + + // Update values from input + if (isset($input['gemini_key'])) { + $envVars['GEMINI_API_KEY'] = $input['gemini_key']; + } + if (isset($input['bring_email'])) { + $envVars['BRING_EMAIL'] = $input['bring_email']; + } + if (isset($input['bring_password'])) { + $envVars['BRING_PASSWORD'] = $input['bring_password']; + } + + // Write .env file + $lines = []; + foreach ($envVars as $key => $val) { + $lines[] = "{$key}={$val}"; + } + $result = file_put_contents($envFile, implode("\n", $lines) . "\n"); + + if ($result !== false) { + echo json_encode(['success' => true]); + } else { + echo json_encode(['success' => false, 'error' => 'Could not write .env file']); + } +} + // ===== GEMINI AI FUNCTIONS ===== function geminiReadExpiry(): void { @@ -728,6 +833,9 @@ function generateRecipe(PDO $db): void { $input = json_decode(file_get_contents('php://input'), true); $mealType = $input['meal'] ?? 'pranzo'; $persons = max(1, intval($input['persons'] ?? 1)); + $options = $input['options'] ?? []; + $appliances = $input['appliances'] ?? []; + $dietaryRestrictions = $input['dietary_restrictions'] ?? ''; // Fetch all inventory items with expiry info $stmt = $db->query(" @@ -774,8 +882,42 @@ function generateRecipe(PDO $db): void { ]; $mealLabel = $mealLabels[$mealType] ?? $mealType; + // Build extra rules from options + $extraRules = []; + $optionLabels = [ + 'veloce' => 'La ricetta deve essere VELOCE: massimo 15-20 minuti totali di preparazione e cottura.', + 'pocafame' => 'L\'utente ha POCA FAME: proponi una porzione leggera, magari uno snack, un\'insalata o qualcosa di semplice e poco abbondante.', + 'scadenze' => 'PRIORITÀ SCADENZE: usa ASSOLUTAMENTE per primi gli ingredienti più vicini alla scadenza o già scaduti (se ancora commestibili).', + 'salutare' => 'Ricetta EXTRA SALUTARE: prediligi ingredienti integrali, tante verdure, pochi grassi, cotture leggere.', + 'comfort' => 'Ricetta COMFORT FOOD: qualcosa di appagante, gustoso e che dia soddisfazione.', + 'zerowaste' => 'ZERO SPRECHI: cerca di usare quanti più ingredienti in scadenza possibile, combina anche ingredienti insoliti pur di non sprecare nulla.' + ]; + foreach ($options as $opt) { + if (isset($optionLabels[$opt])) { + $extraRules[] = $optionLabels[$opt]; + } + } + + $extraRulesText = ''; + if (!empty($extraRules)) { + $extraRulesText = "\n\nPREFERENZE DELL'UTENTE:\n" . implode("\n", $extraRules); + } + + // Appliances + $appliancesText = ''; + if (!empty($appliances)) { + $appliancesText = "\n\nELETTRODOMESTICI DISPONIBILI:\nL'utente dispone di: " . implode(', ', $appliances) . ".\nPuoi usare SOLO questi elettrodomestici (più fornelli e forno che si presumono sempre disponibili). Non suggerire ricette che richiedano elettrodomestici non elencati."; + } + + // Dietary restrictions + $dietaryText = ''; + if (!empty($dietaryRestrictions)) { + $dietaryText = "\n\nRESTRIZIONI ALIMENTARI:\n{$dietaryRestrictions}\nRispetta SEMPRE queste restrizioni."; + } + $prompt = << ` +
+ 🔌 ${escapeHtml(a)} + +
+ `).join(''); +} + +function addAppliance() { + const input = document.getElementById('new-appliance-input'); + const name = (input.value || '').trim(); + if (!name) return; + const s = getSettings(); + if (!s.appliances) s.appliances = []; + if (s.appliances.some(a => a.toLowerCase() === name.toLowerCase())) { + showToast('Elettrodomestico già presente', 'error'); + return; + } + s.appliances.push(name); + saveSettingsToStorage(s); + renderAppliances(s.appliances); + input.value = ''; + showToast('Elettrodomestico aggiunto', 'success'); +} + +function addApplianceQuick(name) { + const s = getSettings(); + if (!s.appliances) s.appliances = []; + if (s.appliances.some(a => a.toLowerCase() === name.toLowerCase())) { + showToast('Già presente', 'error'); + return; + } + s.appliances.push(name); + saveSettingsToStorage(s); + renderAppliances(s.appliances); + showToast(`${name} aggiunto`, 'success'); +} + +function removeAppliance(idx) { + const s = getSettings(); + if (!s.appliances) return; + s.appliances.splice(idx, 1); + saveSettingsToStorage(s); + renderAppliances(s.appliances); +} + +async function saveSettings() { + const s = getSettings(); + s.gemini_key = document.getElementById('setting-gemini-key').value.trim(); + s.bring_email = document.getElementById('setting-bring-email').value.trim(); + s.bring_password = document.getElementById('setting-bring-password').value.trim(); + s.default_persons = parseInt(document.getElementById('setting-default-persons').value) || 1; + s.pref_veloce = document.getElementById('setting-pref-veloce').checked; + s.pref_pocafame = document.getElementById('setting-pref-pocafame').checked; + s.pref_scadenze = document.getElementById('setting-pref-scadenze').checked; + s.pref_healthy = document.getElementById('setting-pref-healthy').checked; + s.pref_comfort = document.getElementById('setting-pref-comfort').checked; + s.pref_zerowaste = document.getElementById('setting-pref-zerowaste').checked; + s.dietary = document.getElementById('setting-dietary').value.trim(); + saveSettingsToStorage(s); + + // Also save to server .env + try { + const result = await api('save_settings', {}, 'POST', { + gemini_key: s.gemini_key, + bring_email: s.bring_email, + bring_password: s.bring_password + }); + const statusEl = document.getElementById('settings-status'); + if (result.success) { + statusEl.className = 'settings-status success'; + statusEl.textContent = '✅ Configurazione salvata!'; + } else { + statusEl.className = 'settings-status error'; + statusEl.textContent = '⚠️ Salvato localmente, errore server: ' + (result.error || ''); + } + statusEl.style.display = 'block'; + setTimeout(() => statusEl.style.display = 'none', 4000); + } catch(e) { + const statusEl = document.getElementById('settings-status'); + statusEl.className = 'settings-status success'; + statusEl.textContent = '✅ Configurazione salvata localmente'; + statusEl.style.display = 'block'; + setTimeout(() => statusEl.style.display = 'none', 4000); + } +} + +function switchSettingsTab(btn, tabId) { + document.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.settings-panel').forEach(p => p.classList.remove('active')); + btn.classList.add('active'); + document.getElementById(tabId).classList.add('active'); +} + +function togglePasswordVisibility(inputId) { + const input = document.getElementById(inputId); + input.type = input.type === 'password' ? 'text' : 'password'; +} + // ===== API HELPER ===== async function api(action, params = {}, method = 'GET', body = null) { let url = `${API_BASE}?action=${action}`; @@ -367,6 +520,7 @@ function showPage(pageId, param = null) { case 'shopping': loadShoppingList(); break; case 'log': loadLog(); break; case 'ai': initAICamera(); break; + case 'settings': loadSettingsUI(); break; } // Stop scanner when leaving scan page @@ -417,13 +571,17 @@ async function loadDashboard() { else if (days <= 7) { badgeText = `${days} giorni`; badgeClass = 'expiring'; } else if (days <= 30) { badgeText = `${days}g`; badgeClass = 'expiring-soon'; } else { const m = Math.round(days/30); badgeText = m <= 1 ? `${days}g` : `~${m} mesi`; badgeClass = 'expiring-later'; } + const qtyDisplay = formatQuantity(item.quantity, item.unit); return `
${escapeHtml(item.name)} ${item.brand ? `${escapeHtml(item.brand)}` : ''}
- ${badgeText} +
+ 📦 ${qtyDisplay} + ${badgeText} +
`; }).join(''); } else { @@ -443,11 +601,13 @@ async function loadDashboard() { else daysText = `Da ${days}g`; const safety = getExpiredSafety(item, days); const locIcon = item.location === 'freezer' ? '❄️' : item.location === 'frigo' ? '🧊' : ''; + const qtyDisplayExp = formatQuantity(item.quantity, item.unit); return `
${locIcon ? locIcon + ' ' : ''}${escapeHtml(item.name)} ${item.brand ? `${escapeHtml(item.brand)}` : ''} + 📦 ${qtyDisplayExp}
${daysText} @@ -589,10 +749,10 @@ function renderInventoryItem(item) { ${item.brand ? `
${escapeHtml(item.brand)}
` : ''}
${locInfo.icon} ${locInfo.label} - ${qtyDisplay} ${expiryBadge}
+ ${qtyDisplay}
`; } @@ -744,7 +904,7 @@ function editInventoryItem(id) {

Modifica ${escapeHtml(item.name)}

-
+
@@ -753,6 +913,12 @@ function editInventoryItem(id) {
+
+ + +
@@ -773,13 +939,14 @@ function editInventoryItem(id) { document.getElementById('modal-overlay').style.display = 'flex'; } -async function submitEditInventory(e, id) { +async function submitEditInventory(e, id, productId) { e.preventDefault(); const qty = parseFloat(document.getElementById('edit-qty').value); const loc = document.getElementById('edit-loc').value; const expiry = document.getElementById('edit-expiry').value || null; + const unit = document.getElementById('edit-unit').value; - await api('inventory_update', {}, 'POST', { id, quantity: qty, location: loc, expiry_date: expiry }); + await api('inventory_update', {}, 'POST', { id, quantity: qty, location: loc, expiry_date: expiry, unit, product_id: productId }); closeModal(); showToast('Aggiornato!', 'success'); refreshCurrentPage(); @@ -1393,9 +1560,6 @@ function showProductAction() { // Ingredients (collapsible) let ingredientsHtml = ''; if (currentProduct.ingredients) { - const ingredShort = currentProduct.ingredients.length > 120 - ? currentProduct.ingredients.substring(0, 120) + '...' - : currentProduct.ingredients; ingredientsHtml = `
📋 Ingredienti @@ -1410,6 +1574,7 @@ function showProductAction() { conservationHtml = `
🧊 ${escapeHtml(currentProduct.conservation)}
`; } + // LARGER product preview document.getElementById('action-product-preview').innerHTML = ` ${currentProduct.image_url ? `` : @@ -1418,6 +1583,7 @@ function showProductAction() {

${escapeHtml(currentProduct.name)}

${currentProduct.brand ? `${escapeHtml(currentProduct.brand)}` : ''}

+ ${currentProduct.weight_info ? `

⚖️ ${escapeHtml(currentProduct.weight_info)}

` : ''} ${currentProduct.barcode ? `

📊 ${currentProduct.barcode}

` : ''}
`; @@ -1467,7 +1633,6 @@ function showProductAction() {
`; editInfoEl.style.display = 'block'; - // Focus name field if unknown if (isUnknown) { setTimeout(() => document.getElementById('edit-action-name')?.focus(), 100); } @@ -1482,8 +1647,7 @@ function showProductAction() { const container = document.getElementById('action-product-preview').parentElement; extraInfoEl = document.createElement('div'); extraInfoEl.id = 'action-product-details'; - // Insert after preview, before action buttons - const actionBtns = document.querySelector('#page-action .action-buttons'); + const actionBtns = document.getElementById('action-buttons-container'); actionBtns.parentElement.insertBefore(extraInfoEl, actionBtns); } @@ -1502,9 +1666,205 @@ function showProductAction() { extraInfoEl.innerHTML = ''; } + // === CHECK INVENTORY FOR THIS PRODUCT === + checkInventoryForProduct(currentProduct.id).then(inventoryItems => { + const statusBar = document.getElementById('action-inventory-status'); + const btnsContainer = document.getElementById('action-buttons-container'); + + if (inventoryItems.length > 0) { + // Product IS in inventory - show status and 3 buttons + statusBar.style.display = 'block'; + let totalQty = 0; + const unit = inventoryItems[0].unit || 'pz'; + const invHtml = inventoryItems.map(inv => { + const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location }; + const qtyStr = formatQuantity(inv.quantity, inv.unit); + totalQty += parseFloat(inv.quantity); + let expiryStr = ''; + if (inv.expiry_date) { + const d = daysUntilExpiry(inv.expiry_date); + if (d < 0) expiryStr = ` · ⚠️ Scaduto da ${Math.abs(d)}g`; + else if (d <= 3) expiryStr = ` · 🔴 Scade tra ${d}g`; + else if (d <= 7) expiryStr = ` · 🟡 Scade tra ${d}g`; + else expiryStr = ` · 📅 ${formatDate(inv.expiry_date)}`; + } + return `
${locInfo.icon} ${locInfo.label}${expiryStr}${qtyStr}
`; + }).join(''); + + const totalStr = formatQuantity(totalQty, unit); + + statusBar.innerHTML = ` +
+ 📦 Ce l'hai già! + ${totalStr} +
+
${invHtml}
+ `; + + btnsContainer.className = 'action-buttons-3col'; + btnsContainer.innerHTML = ` + + + + `; + } else { + // Product NOT in inventory - show only AGGIUNGI + statusBar.style.display = 'none'; + btnsContainer.className = 'action-buttons'; + btnsContainer.innerHTML = ` + + `; + } + }); + showPage('action'); } +// Check if product exists in inventory +async function checkInventoryForProduct(productId) { + try { + const data = await api('inventory_list'); + return (data.inventory || []).filter(i => i.product_id == productId); + } catch(e) { + return []; + } +} + +// === THROW AWAY FORM === +function showThrowForm() { + // Open a modal to ask how much to throw away + api('inventory_list').then(data => { + const items = (data.inventory || []).filter(i => i.product_id == currentProduct.id); + if (items.length === 0) { + showToast('Prodotto non nell\'inventario', 'error'); + return; + } + + const totalQty = items.reduce((sum, i) => sum + parseFloat(i.quantity), 0); + const unit = items[0].unit || 'pz'; + const qtyDisplay = formatQuantity(totalQty, unit); + + let locOptionsHtml = items.map(inv => { + const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location }; + return `
${locInfo.icon} ${locInfo.label}${formatQuantity(inv.quantity, inv.unit)}
`; + }).join(''); + + document.getElementById('modal-content').innerHTML = ` + +
+ ${currentProduct.image_url ? + `` : + `${CATEGORY_ICONS[mapToLocalCategory(currentProduct.category, currentProduct.name)] || '📦'}` + } +
+

${escapeHtml(currentProduct.name)}

+

Disponibile: ${qtyDisplay}

+
+
+
+
${locOptionsHtml}
+
+
+ +
oppure specifica la quantità:
+
+ +
+ ${items.map((inv, idx) => { + const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location }; + return ``; + }).join('')} +
+ +
+
+ +
+ + + +
+
+ +
+ `; + document.getElementById('modal-overlay').style.display = 'flex'; + }); +} + +function selectThrowLocation(btn, loc) { + btn.parentElement.querySelectorAll('.loc-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + document.getElementById('throw-location').value = loc; +} + +async function throwAll() { + closeModal(); + showLoading(true); + try { + const result = await api('inventory_use', {}, 'POST', { + product_id: currentProduct.id, + use_all: true, + location: '__all__', + notes: 'Buttato' + }); + showLoading(false); + if (result.success) { + showToast(`🗑️ ${currentProduct.name} buttato!`, 'success'); + showPage('dashboard'); + } else { + showToast(result.error || 'Errore', 'error'); + } + } catch(e) { + showLoading(false); + showToast('Errore di connessione', 'error'); + } +} + +async function throwPartial() { + const qty = parseFloat(document.getElementById('throw-quantity').value) || 1; + const loc = document.getElementById('throw-location').value; + closeModal(); + showLoading(true); + try { + const result = await api('inventory_use', {}, 'POST', { + product_id: currentProduct.id, + quantity: qty, + location: loc, + notes: 'Buttato' + }); + showLoading(false); + if (result.success) { + showToast(`🗑️ Buttato ${qty} ${currentProduct.unit || 'pz'} di ${currentProduct.name}`, 'success'); + showPage('dashboard'); + } else { + showToast(result.error || 'Errore', 'error'); + } + } catch(e) { + showLoading(false); + showToast('Errore di connessione', 'error'); + } +} + async function saveEditedProductInfo() { const name = (document.getElementById('edit-action-name')?.value || '').trim(); if (!name) { @@ -2727,6 +3087,7 @@ const MEAL_LABELS = { function openRecipeDialog() { const meal = getMealType(); + const settings = getSettings(); document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; document.getElementById('recipe-overlay').style.display = 'flex'; @@ -2742,13 +3103,33 @@ function openRecipeDialog() { } } catch (e) { /* ignore parse errors */ } - // No valid cache — show ask form - document.getElementById('recipe-persons').value = 1; + // Pre-fill persons from settings + document.getElementById('recipe-persons').value = settings.default_persons || 1; + + // Pre-select option chips from settings + const prefMap = { + 'veloce': 'recipe-opt-veloce', + 'pocafame': 'recipe-opt-pocafame', + 'scadenze': 'recipe-opt-scadenze', + 'salutare': 'recipe-opt-healthy', + 'comfort': 'recipe-opt-comfort', + 'zerowaste': 'recipe-opt-zerowaste' + }; + Object.entries(prefMap).forEach(([key, id]) => { + const cb = document.getElementById(id); + if (cb) cb.checked = settings.recipe_prefs && settings.recipe_prefs.includes(key); + }); + document.getElementById('recipe-ask').style.display = ''; document.getElementById('recipe-loading').style.display = 'none'; document.getElementById('recipe-result').style.display = 'none'; } +// Toggle recipe option chip +function toggleRecipeOption(btn) { + btn.classList.toggle('active'); +} + function closeRecipeDialog() { document.getElementById('recipe-overlay').style.display = 'none'; } @@ -2891,13 +3272,35 @@ function regenerateRecipe() { async function generateRecipe() { const meal = getMealType(); const persons = parseInt(document.getElementById('recipe-persons').value) || 1; + const settings = getSettings(); + + // Gather active options from checkboxes + const options = []; + const optMap = { + 'recipe-opt-veloce': 'veloce', + 'recipe-opt-pocafame': 'pocafame', + 'recipe-opt-scadenze': 'scadenze', + 'recipe-opt-healthy': 'salutare', + 'recipe-opt-comfort': 'comfort', + 'recipe-opt-zerowaste': 'zerowaste' + }; + Object.entries(optMap).forEach(([id, key]) => { + const cb = document.getElementById(id); + if (cb && cb.checked) options.push(key); + }); document.getElementById('recipe-ask').style.display = 'none'; document.getElementById('recipe-loading').style.display = ''; document.getElementById('recipe-result').style.display = 'none'; try { - const result = await api('generate_recipe', {}, 'POST', { meal, persons }); + const result = await api('generate_recipe', {}, 'POST', { + meal, + persons, + options, + appliances: settings.appliances || [], + dietary_restrictions: settings.dietary_restrictions || '' + }); if (!result.success) { document.getElementById('recipe-loading').style.display = 'none'; diff --git a/data/dispensa.db b/data/dispensa.db index 59131861aaf41e5806f7270b38097de0f9ba0e99..7ef897099341f9e098b8bd57f6feec49acc7e7de 100644 GIT binary patch delta 580 zcmZo@U~On%-N2&29L)7}GP{C17uQoBN&Xc6GOq8NLlkmt|1z%R|am7$9_npKARId>125r+c1 zDQ66u9@8|&WfL3Ic$y@c`54so&E*@-1t%}tGmWXBYO?L#JT5~8Lt`rgLn}kG&D|RW zcs6g_>uto%U&)}(x|V@|58M9DJRdf*i8wKFFfht8DjFLzr(_lvq~;YT3QX?#uZFCh z`Fj}$3j+g#^TbB`&3pcP2r!E>T2E&eU{q&evSGB|*vQGK(O4JE%b+eP9^Gip&sc73 zj1o%DnfXbHMahY|sri%n_r>S{!%rU=fcl^i)K4qQ&nt<~EYUMJ&@(bH(90@FPp=I~ zEzY-6Ff=kvpFD4$^km6L{827O21aJO2FALEK%W^|8Jk*}SYQ!qVhjR0L|Zzr(UhO5 z5bO{{mk6Lxr|G2s0kg0RZ>Tnh^j1 delta 251 zcmZo@U~On%-N2&29LUW%nO(u1jYpC{g}-cbghDUZ#0eUkIkmruFa~aNz@sAjuZvta z1HUxyR-Rcr4lImJXSfPE#o1Zeq&OT|g&C_Ex+gZKZ(g=hfQMUxnU6tT-(0@YoPYAP z4bvu<-{zZ~uvuU--y^BXHoFBTmu^(&GEguyv@$TYGBDe`b+3;RH`{&&b=I{E{CoH- zH}ibh%*MjNz~C~m(P1<5_c9J)Mkm%phYo3iJ>hXAuM Wv%_?D0Y>$WjY7=Z9fTPV=l}p*B1fqJ diff --git a/index.html b/index.html index 2a8fe36..f6b0178 100644 --- a/index.html +++ b/index.html @@ -131,8 +131,9 @@

Cosa vuoi fare?

-
-
+
+ +
+ +
+ +
+ + + + +
+
+ +
+
+

🤖 Google Gemini AI

+

Chiave API per identificazione prodotti, scadenze e ricette.

+
+ + + +
+
+
+ +
+
+

🛒 Bring! Shopping List

+

Credenziali per l'integrazione con la lista della spesa Bring!

+
+ + +
+
+ + + +
+
+
+ +
+
+

🍳 Preferenze Ricette

+

Configura le opzioni predefinite per la generazione delle ricette.

+
+ +
+ + + +
+
+
+ +
+ + + + + + +
+
+
+ + +
+
+
+ +
+
+

🔌 Elettrodomestici Disponibili

+

Indica gli elettrodomestici che hai a disposizione. Saranno considerati nella generazione delle ricette.

+
+
+
+ + +
+
+
+

Aggiungi velocemente:

+
+ + + + + + + + + + +
+
+
+
+
+ + +
+ @@ -508,6 +614,10 @@ 📒 Log + @@ -524,6 +634,17 @@
+
+ +
+ + + + + + +
+