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}
+
+
+
+ ${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 @@
-
+