diff --git a/assets/css/style.css b/assets/css/style.css index 50b3685..129a1fd 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -1672,6 +1672,10 @@ body { padding: 0; } +#modal-overlay { + z-index: 250; +} + .modal-content { background: var(--bg-card); border-radius: var(--radius) var(--radius) 0 0; diff --git a/assets/js/app.js b/assets/js/app.js index 63cf87b..159409b 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -4937,48 +4937,285 @@ function adjustRecipePersons(delta) { input.value = val; } +let _recipeUseContext = null; // { idx, productId, btn, qtyNumber } +let _recipeUseConfMode = null; + async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) { if (btn.disabled) return; if (!qtyNumber || qtyNumber <= 0) qtyNumber = 1; + + _recipeUseContext = { idx, productId, btn, qtyNumber }; + _recipeUseConfMode = null; + + // Fetch inventory to build the modal + try { + const data = await api('inventory_list'); + const items = (data.inventory || []).filter(i => i.product_id == productId); + + if (items.length === 0) { + showToast('⚠️ Prodotto non trovato in inventario', 'error'); + return; + } + + const unit = items[0].unit || 'pz'; + const pkgSize = parseFloat(items[0].default_quantity) || 0; + const pkgUnit = items[0].package_unit || ''; + const isConf = unit === 'conf' && pkgSize > 0 && pkgUnit; + + // Find opened package location + const openedItem = items.find(i => { + const q = parseFloat(i.quantity); + const dq = parseFloat(i.default_quantity) || 0; + if (i.unit === 'conf' && dq > 0) return q !== Math.floor(q); + if (dq > 0) return Math.abs(q - Math.round(q / dq) * dq) > dq * 0.02; + return false; + }); + const defaultLoc = openedItem ? openedItem.location : (items.find(i => i.location === location) ? location : items[0].location); + + // Build location buttons + const productLocations = [...new Set(items.map(i => i.location))]; + const locButtons = productLocations.map(loc => { + const locInfo = LOCATIONS[loc] || { icon: '📦', label: loc }; + const locItems = items.filter(i => i.location === loc); + const locQty = locItems.reduce((s, i) => s + parseFloat(i.quantity), 0); + const qtyLabel = formatQuantity(locQty, unit, pkgSize, pkgUnit); + return ``; + }).join(''); + + // Build quantity controls + let qtySection = ''; + let defaultQtyValue = qtyNumber; + + if (isConf) { + const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0); + const totalSub = totalConf * pkgSize; + const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; + const subLabel = unitLabels[pkgUnit] || pkgUnit; + _recipeUseConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel, _activeUnit: 'sub' }; + + // qtyNumber from recipe is in sub-units (g, ml) + const step = getSubUnitStep(pkgUnit); + defaultQtyValue = qtyNumber; + + qtySection = ` +
+ + +
+

Quantità in ${subLabel} (totale: ${Math.round(totalSub)}${subLabel})

+
+ + + +
`; + } else { + const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml' }; + const unitLabel = unitLabels[unit] || unit; + qtySection = ` +

Quantità da usare (${unitLabel}):

+
+ + + +
`; + } + + // Available info + const availInfo = items.map(i => { + const loc = LOCATIONS[i.location] || { icon: '📦', label: i.location }; + return `${loc.icon} ${formatQuantity(i.quantity, i.unit, i.default_quantity, i.package_unit)}`; + }).join(' · '); + + document.getElementById('modal-content').innerHTML = ` + +
+

${escapeHtml(items[0].name)}

+

📦 ${availInfo}

+
+ +
${locButtons}
+ +
+
+ + ${qtySection} +
+ + +
+ `; + document.getElementById('modal-overlay').style.display = 'flex'; + + } catch (err) { + console.error('useRecipeIngredient error:', err); + showToast('Errore nel caricamento', 'error'); + } +} + +function selectRecipeUseLoc(btn, loc) { + btn.parentElement.querySelectorAll('.loc-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + document.getElementById('ruse-location').value = loc; +} + +function switchRecipeUseUnit(mode) { + if (!_recipeUseConfMode) return; + const subBtn = document.getElementById('ruse-unit-sub'); + const confBtn = document.getElementById('ruse-unit-conf'); + const qtyInput = document.getElementById('ruse-quantity'); + const hint = document.getElementById('ruse-hint'); + + if (mode === 'sub') { + subBtn.classList.add('active'); + confBtn.classList.remove('active'); + _recipeUseConfMode._activeUnit = 'sub'; + const step = getSubUnitStep(_recipeUseConfMode.packageUnit); + qtyInput.value = _recipeUseContext.qtyNumber || step; + qtyInput.step = step; + qtyInput.min = step; + hint.textContent = `Quantità in ${_recipeUseConfMode.subLabel} (totale: ${Math.round(_recipeUseConfMode.totalSub)}${_recipeUseConfMode.subLabel})`; + } else { + confBtn.classList.add('active'); + subBtn.classList.remove('active'); + _recipeUseConfMode._activeUnit = 'conf'; + qtyInput.value = 1; + qtyInput.step = 0.5; + qtyInput.min = 0.5; + hint.textContent = `Confezioni da ${_recipeUseConfMode.packageSize}${_recipeUseConfMode.subLabel} (hai ${_recipeUseConfMode.totalConf.toFixed(1)} conf)`; + } +} + +function adjustRecipeUseQty(direction) { + const input = document.getElementById('ruse-quantity'); + let val = parseFloat(input.value) || 0; + let step; + if (_recipeUseConfMode && _recipeUseConfMode._activeUnit === 'sub') { + step = getSubUnitStep(_recipeUseConfMode.packageUnit); + } else if (_recipeUseConfMode && _recipeUseConfMode._activeUnit === 'conf') { + step = 0.5; + } else { + step = 0.5; + } + val = Math.max(step, val + direction * step); + input.value = Math.round(val * 1000) / 1000; +} + +async function submitRecipeUse(useAll) { + if (!_recipeUseContext) return; + const { idx, productId, btn } = _recipeUseContext; + const location = document.getElementById('ruse-location').value; + + let qty; + if (useAll) { + qty = 0; // API handles use_all + } else { + qty = parseFloat(document.getElementById('ruse-quantity').value) || 1; + if (_recipeUseConfMode && _recipeUseConfMode._activeUnit === 'sub') { + qty = qty / _recipeUseConfMode.packageSize; + } + } + + closeModal(); btn.disabled = true; btn.textContent = '⏳...'; - + try { const result = await api('inventory_use', {}, 'POST', { product_id: productId, - quantity: qtyNumber, - use_all: false, + quantity: qty, + use_all: useAll, location: location }); - + if (result.success) { const li = document.getElementById(`recipe-ing-${idx}`); - if (li) { - li.classList.add('recipe-ing-used'); - } + if (li) li.classList.add('recipe-ing-used'); btn.textContent = '✔️ Scalato'; btn.classList.add('btn-used'); - - // Persist used state in cached recipe + if (_cachedRecipe && _cachedRecipe.recipe && _cachedRecipe.recipe.ingredients && _cachedRecipe.recipe.ingredients[idx]) { _cachedRecipe.recipe.ingredients[idx].used = true; } - + showToast('📦 Ingrediente scalato dalla dispensa!', 'success'); if (result.added_to_bring) { setTimeout(() => showToast('🛒 Prodotto finito → aggiunto a Bring!', 'info'), 1500); } + + // Offer to move opened package (stays on recipe page, modal over recipe overlay) + if (result.remaining > 0) { + setTimeout(() => showRecipeMoveModal(productId, location, result.remaining, result.opened_id), 400); + } } else { btn.disabled = false; btn.textContent = '📦 Usa'; showToast(result.error || 'Errore nello scalare', 'error'); } } catch (err) { - console.error('Use ingredient error:', err); + console.error('Recipe use error:', err); btn.disabled = false; btn.textContent = '📦 Usa'; showToast('Errore di connessione', 'error'); } + _recipeUseContext = null; +} + +function showRecipeMoveModal(productId, fromLoc, remaining, openedId) { + const otherLocs = Object.entries(LOCATIONS).filter(([k]) => k !== fromLoc); + const locButtons = otherLocs.map(([k, v]) => + `` + ).join(''); + + document.getElementById('modal-content').innerHTML = ` + +
+

Vuoi spostare ${openedId ? 'la confezione aperta' : 'il resto'} in un'altra posizione?

+
${locButtons}
+ +
+ `; + document.getElementById('modal-overlay').style.display = 'flex'; +} + +async function confirmRecipeMove(productId, fromLoc, toLoc, openedId) { + closeModal(); + try { + if (openedId) { + let days = estimateExpiryDays({ name: '', category: '' }, toLoc); + await api('inventory_update', {}, 'POST', { + id: openedId, + location: toLoc, + expiry_date: addDays(days), + product_id: productId, + }); + } else { + const data = await api('inventory_list'); + const item = (data.inventory || []).find(i => i.product_id == productId && i.location === fromLoc && parseFloat(i.quantity) > 0); + if (item) { + let days = estimateExpiryDays({ name: item.name || '', category: item.category || '' }, toLoc); + if (item.vacuum_sealed) days = getVacuumExpiryDays(days); + await api('inventory_update', {}, 'POST', { + id: item.id, + location: toLoc, + expiry_date: addDays(days), + product_id: productId, + }); + } + } + showToast(`📦 Spostato in ${LOCATIONS[toLoc]?.label || toLoc}`, 'success'); + } catch (e) { + console.error('Recipe move error:', e); + } } function renderRecipe(r) { diff --git a/data/dispensa.db b/data/dispensa.db index 7841071..fd85752 100644 Binary files a/data/dispensa.db and b/data/dispensa.db differ diff --git a/index.html b/index.html index 487238a..d46c342 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,7 @@ Dispensa Manager - + @@ -911,6 +911,6 @@ - +