feat: ricetta persistente + quantità corretta per scalare ingredienti

- Ricetta cachata in localStorage: riaprendo mostra la stessa ricetta
- Si rigenera solo se cambia tipo pasto (colazione/pranzo/cena) o bottone 'Generane un'altra'
- Prompt Gemini aggiornato: richiede qty_number nella stessa unità di misura dell'inventario
- Bottone 'Usa' ora scala la quantità effettiva della ricetta (non più sempre 1)
- Estratta renderRecipe() per riutilizzo con cache
- Aggiunto bottone '🔄 Generane un'altra' nel risultato ricetta
This commit is contained in:
dadaloop82
2026-03-10 17:36:34 +00:00
parent fa4dc6ae88
commit bae9af0fe5
3 changed files with 87 additions and 49 deletions
+3 -2
View File
@@ -741,6 +741,7 @@ REGOLE IMPORTANTI:
4. Adatta le quantità per $persons persona/e
5. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile
6. La ricetta deve essere adatta al pasto: $mealLabel
7. IMPORTANTE - QUANTITÀ NUMERICHE: per ogni ingrediente dalla dispensa, il campo "qty_number" DEVE contenere il valore NUMERICO da scalare dall'inventario, espresso nella STESSA unità di misura della dispensa. Esempio: se in dispensa c'è "Farina: 1000 g" e la ricetta richiede 200g, qty_number = 200. Se "Riso: 2 kg" e servono 300g, qty_number = 0.3. Per ingredienti non dalla dispensa, qty_number = 0.
INGREDIENTI DISPONIBILI IN DISPENSA:
$ingredientsText
@@ -755,8 +756,8 @@ Rispondi SOLO con un JSON valido in questo formato esatto (senza markdown, senza
"tags": ["sano", "veloce", "..."],
"expiry_note": "Nota sugli ingredienti in scadenza usati (o stringa vuota)",
"ingredients": [
{"name": "nome ingrediente", "qty": "quantità per $persons persone", "from_pantry": true},
{"name": "sale", "qty": "q.b.", "from_pantry": false}
{"name": "nome ingrediente", "qty": "quantità leggibile (es: 200 g)", "qty_number": 200, "from_pantry": true},
{"name": "sale", "qty": "q.b.", "qty_number": 0, "from_pantry": false}
],
"steps": [
"Passo 1: descrizione dettagliata",
+81 -47
View File
@@ -2637,11 +2637,25 @@ const MEAL_LABELS = {
function openRecipeDialog() {
const meal = getMealType();
document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta';
document.getElementById('recipe-overlay').style.display = 'flex';
// Check for cached recipe matching current meal type
try {
const cached = JSON.parse(localStorage.getItem('cachedRecipe') || 'null');
if (cached && cached.meal === meal && cached.recipe) {
document.getElementById('recipe-ask').style.display = 'none';
document.getElementById('recipe-loading').style.display = 'none';
renderRecipe(cached.recipe);
document.getElementById('recipe-result').style.display = '';
return;
}
} catch (e) { /* ignore parse errors */ }
// No valid cache — show ask form
document.getElementById('recipe-persons').value = 1;
document.getElementById('recipe-ask').style.display = '';
document.getElementById('recipe-loading').style.display = 'none';
document.getElementById('recipe-result').style.display = 'none';
document.getElementById('recipe-overlay').style.display = 'flex';
}
function closeRecipeDialog() {
@@ -2655,15 +2669,16 @@ function adjustRecipePersons(delta) {
input.value = val;
}
async function useRecipeIngredient(idx, productId, location, btn) {
async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
if (btn.disabled) return;
if (!qtyNumber || qtyNumber <= 0) qtyNumber = 1;
btn.disabled = true;
btn.textContent = '⏳...';
try {
const result = await api('inventory_use', {}, 'POST', {
product_id: productId,
quantity: 1,
quantity: qtyNumber,
use_all: false,
location: location
});
@@ -2689,6 +2704,66 @@ async function useRecipeIngredient(idx, productId, location, btn) {
}
}
function renderRecipe(r) {
let html = `<h2>${r.title}</h2>`;
// Meta tags
html += '<div class="recipe-meta">';
html += `<span class="recipe-tag">${MEAL_LABELS[r.meal] || r.meal}</span>`;
html += `<span class="recipe-tag">👥 ${r.persons} pers.</span>`;
if (r.prep_time) html += `<span class="recipe-tag">🔪 ${r.prep_time}</span>`;
if (r.cook_time) html += `<span class="recipe-tag">🔥 ${r.cook_time}</span>`;
if (r.tags) r.tags.forEach(t => { html += `<span class="recipe-tag">${t}</span>`; });
html += '</div>';
// Expiry note
if (r.expiry_note) {
html += `<div class="recipe-expiry-note">⚠️ ${r.expiry_note}</div>`;
}
// Ingredients
html += '<h3>🧾 Ingredienti</h3><ul class="recipe-ingredients">';
(r.ingredients || []).forEach((ing, idx) => {
if (ing.from_pantry && ing.product_id) {
const qtyNum = ing.qty_number || 0;
const loc = (ing.location || 'dispensa').replace(/'/g, "\\'");
html += `<li class="recipe-ingredient" id="recipe-ing-${idx}">`;
html += `<span class="recipe-ing-text"><strong>${ing.name}</strong>: ${ing.qty} ✅</span>`;
html += `<button class="btn-use-ingredient" onclick="useRecipeIngredient(${idx}, ${ing.product_id}, '${loc}', ${qtyNum}, this)" title="Scala dalla dispensa">📦 Usa</button>`;
html += `</li>`;
} else {
const pantryIcon = ing.from_pantry ? ' ✅' : ' 🛒';
html += `<li class="recipe-ingredient"><span class="recipe-ing-text"><strong>${ing.name}</strong>: ${ing.qty}${pantryIcon}</span></li>`;
}
});
html += '</ul>';
// Steps
html += '<h3>👨‍🍳 Procedimento</h3><ol>';
(r.steps || []).forEach(step => {
const cleanStep = step.replace(/^Passo\s*\d+\s*:\s*/i, '');
html += `<li>${cleanStep}</li>`;
});
html += '</ol>';
// Nutrition note
if (r.nutrition_note) {
html += `<p style="color:var(--text-muted);font-size:0.85rem;margin-top:12px">💡 ${r.nutrition_note}</p>`;
}
document.getElementById('recipe-content').innerHTML = html;
}
function regenerateRecipe() {
localStorage.removeItem('cachedRecipe');
document.getElementById('recipe-result').style.display = 'none';
document.getElementById('recipe-loading').style.display = 'none';
const meal = getMealType();
document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta';
document.getElementById('recipe-persons').value = 1;
document.getElementById('recipe-ask').style.display = '';
}
async function generateRecipe() {
const meal = getMealType();
const persons = parseInt(document.getElementById('recipe-persons').value) || 1;
@@ -2712,52 +2787,11 @@ async function generateRecipe() {
}
const r = result.recipe;
let html = `<h2>${r.title}</h2>`;
renderRecipe(r);
// Meta tags
html += '<div class="recipe-meta">';
html += `<span class="recipe-tag">${MEAL_LABELS[r.meal] || r.meal}</span>`;
html += `<span class="recipe-tag">👥 ${r.persons} pers.</span>`;
if (r.prep_time) html += `<span class="recipe-tag">🔪 ${r.prep_time}</span>`;
if (r.cook_time) html += `<span class="recipe-tag">🔥 ${r.cook_time}</span>`;
if (r.tags) r.tags.forEach(t => { html += `<span class="recipe-tag">${t}</span>`; });
html += '</div>';
// Cache the recipe for this meal type
localStorage.setItem('cachedRecipe', JSON.stringify({ meal, recipe: r }));
// Expiry note
if (r.expiry_note) {
html += `<div class="recipe-expiry-note">⚠️ ${r.expiry_note}</div>`;
}
// Ingredients
html += '<h3>🧾 Ingredienti</h3><ul class="recipe-ingredients">';
(r.ingredients || []).forEach((ing, idx) => {
if (ing.from_pantry && ing.product_id) {
html += `<li class="recipe-ingredient" id="recipe-ing-${idx}">`;
html += `<span class="recipe-ing-text"><strong>${ing.name}</strong>: ${ing.qty} ✅</span>`;
html += `<button class="btn-use-ingredient" onclick="useRecipeIngredient(${idx}, ${ing.product_id}, '${(ing.location || 'dispensa').replace(/'/g, "\\'")}', this)" title="Scala dalla dispensa">📦 Usa</button>`;
html += `</li>`;
} else {
const pantryIcon = ing.from_pantry ? ' ✅' : ' 🛒';
html += `<li class="recipe-ingredient"><span class="recipe-ing-text"><strong>${ing.name}</strong>: ${ing.qty}${pantryIcon}</span></li>`;
}
});
html += '</ul>';
// Steps
html += '<h3>👨‍🍳 Procedimento</h3><ol>';
(r.steps || []).forEach(step => {
// Remove leading "Passo N:" if present
const cleanStep = step.replace(/^Passo\s*\d+\s*:\s*/i, '');
html += `<li>${cleanStep}</li>`;
});
html += '</ol>';
// Nutrition note
if (r.nutrition_note) {
html += `<p style="color:var(--text-muted);font-size:0.85rem;margin-top:12px">💡 ${r.nutrition_note}</p>`;
}
document.getElementById('recipe-content').innerHTML = html;
document.getElementById('recipe-loading').style.display = 'none';
document.getElementById('recipe-result').style.display = '';
+3
View File
@@ -522,6 +522,9 @@
</div>
<div id="recipe-result" style="display:none" class="recipe-result">
<div id="recipe-content"></div>
<button class="btn btn-large btn-secondary full-width mt-2" onclick="regenerateRecipe()">
🔄 Generane un'altra
</button>
<button class="btn btn-large btn-primary full-width mt-2" onclick="closeRecipeDialog()">
✅ Chiudi
</button>