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 4. Adatta le quantità per $persons persona/e
5. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile 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 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: INGREDIENTI DISPONIBILI IN DISPENSA:
$ingredientsText $ingredientsText
@@ -755,8 +756,8 @@ Rispondi SOLO con un JSON valido in questo formato esatto (senza markdown, senza
"tags": ["sano", "veloce", "..."], "tags": ["sano", "veloce", "..."],
"expiry_note": "Nota sugli ingredienti in scadenza usati (o stringa vuota)", "expiry_note": "Nota sugli ingredienti in scadenza usati (o stringa vuota)",
"ingredients": [ "ingredients": [
{"name": "nome ingrediente", "qty": "quantità per $persons persone", "from_pantry": true}, {"name": "nome ingrediente", "qty": "quantità leggibile (es: 200 g)", "qty_number": 200, "from_pantry": true},
{"name": "sale", "qty": "q.b.", "from_pantry": false} {"name": "sale", "qty": "q.b.", "qty_number": 0, "from_pantry": false}
], ],
"steps": [ "steps": [
"Passo 1: descrizione dettagliata", "Passo 1: descrizione dettagliata",
+81 -47
View File
@@ -2637,11 +2637,25 @@ const MEAL_LABELS = {
function openRecipeDialog() { function openRecipeDialog() {
const meal = getMealType(); const meal = getMealType();
document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; 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-persons').value = 1;
document.getElementById('recipe-ask').style.display = ''; document.getElementById('recipe-ask').style.display = '';
document.getElementById('recipe-loading').style.display = 'none'; document.getElementById('recipe-loading').style.display = 'none';
document.getElementById('recipe-result').style.display = 'none'; document.getElementById('recipe-result').style.display = 'none';
document.getElementById('recipe-overlay').style.display = 'flex';
} }
function closeRecipeDialog() { function closeRecipeDialog() {
@@ -2655,15 +2669,16 @@ function adjustRecipePersons(delta) {
input.value = val; input.value = val;
} }
async function useRecipeIngredient(idx, productId, location, btn) { async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
if (btn.disabled) return; if (btn.disabled) return;
if (!qtyNumber || qtyNumber <= 0) qtyNumber = 1;
btn.disabled = true; btn.disabled = true;
btn.textContent = '⏳...'; btn.textContent = '⏳...';
try { try {
const result = await api('inventory_use', {}, 'POST', { const result = await api('inventory_use', {}, 'POST', {
product_id: productId, product_id: productId,
quantity: 1, quantity: qtyNumber,
use_all: false, use_all: false,
location: location 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() { async function generateRecipe() {
const meal = getMealType(); const meal = getMealType();
const persons = parseInt(document.getElementById('recipe-persons').value) || 1; const persons = parseInt(document.getElementById('recipe-persons').value) || 1;
@@ -2712,52 +2787,11 @@ async function generateRecipe() {
} }
const r = result.recipe; const r = result.recipe;
let html = `<h2>${r.title}</h2>`; renderRecipe(r);
// Meta tags // Cache the recipe for this meal type
html += '<div class="recipe-meta">'; localStorage.setItem('cachedRecipe', JSON.stringify({ meal, recipe: r }));
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) {
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-loading').style.display = 'none';
document.getElementById('recipe-result').style.display = ''; document.getElementById('recipe-result').style.display = '';
+3
View File
@@ -522,6 +522,9 @@
</div> </div>
<div id="recipe-result" style="display:none" class="recipe-result"> <div id="recipe-result" style="display:none" class="recipe-result">
<div id="recipe-content"></div> <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()"> <button class="btn btn-large btn-primary full-width mt-2" onclick="closeRecipeDialog()">
✅ Chiudi ✅ Chiudi
</button> </button>