feat: confezioni (conf) chiede dimensione singola confezione

- Nuovo campo package_unit in DB (migrazione automatica)
- Form aggiungi/modifica: quando si seleziona 'conf', appare campo per
  specificare il contenuto della singola confezione (es. 300g, 2L)
- Visualizzazione: '3 conf (da 300g)' in inventario, dettaglio, butta
- formatQuantity aggiornato con supporto package_unit
- API: salva/restituisce package_unit in tutti gli endpoint
- Ricette e chat: contesto arricchito con info confezione
- CSS: stili per il nuovo campo conf-size
This commit is contained in:
dadaloop82
2026-03-11 15:43:44 +00:00
parent af3b5941a0
commit bf2e137502
6 changed files with 210 additions and 34 deletions
+13
View File
@@ -16,6 +16,10 @@ function getDB(): PDO {
if ($isNew) {
initializeDB($db);
}
// Run migrations
migrateDB($db);
return $db;
}
@@ -64,3 +68,12 @@ function initializeDB(PDO $db): void {
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(created_at);
");
}
function migrateDB(PDO $db): void {
// Add package_unit column if missing
$cols = $db->query("PRAGMA table_info(products)")->fetchAll();
$colNames = array_column($cols, 'name');
if (!in_array('package_unit', $colNames)) {
$db->exec("ALTER TABLE products ADD COLUMN package_unit TEXT DEFAULT ''");
}
}
+31 -10
View File
@@ -303,28 +303,28 @@ function saveProduct(PDO $db): void {
// Update existing
$stmt = $db->prepare("
UPDATE products SET name=?, brand=?, category=?, image_url=?, unit=?,
default_quantity=?, notes=?, barcode=?, updated_at=CURRENT_TIMESTAMP
default_quantity=?, notes=?, barcode=?, package_unit=?, updated_at=CURRENT_TIMESTAMP
WHERE id=?
");
$stmt->execute([
$input['name'], $input['brand'] ?? '', $input['category'] ?? '',
$input['image_url'] ?? '', $input['unit'] ?? 'pz',
$input['default_quantity'] ?? 1, $input['notes'] ?? '',
$input['barcode'] ?? null, $input['id']
$input['barcode'] ?? null, $input['package_unit'] ?? '', $input['id']
]);
echo json_encode(['success' => true, 'id' => $input['id']]);
} else {
// Insert new
$stmt = $db->prepare("
INSERT INTO products (barcode, name, brand, category, image_url, unit, default_quantity, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO products (barcode, name, brand, category, image_url, unit, default_quantity, notes, package_unit)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$barcode = !empty($input['barcode']) ? $input['barcode'] : null;
$stmt->execute([
$barcode, $input['name'], $input['brand'] ?? '',
$input['category'] ?? '', $input['image_url'] ?? '',
$input['unit'] ?? 'pz', $input['default_quantity'] ?? 1,
$input['notes'] ?? ''
$input['notes'] ?? '', $input['package_unit'] ?? ''
]);
echo json_encode(['success' => true, 'id' => $db->lastInsertId()]);
}
@@ -369,7 +369,7 @@ function searchProducts(PDO $db): void {
function listInventory(PDO $db): void {
$location = $_GET['location'] ?? '';
$query = "
SELECT i.*, p.name, p.brand, p.category, p.image_url, p.unit, p.barcode, p.default_quantity
SELECT i.*, p.name, p.brand, p.category, p.image_url, p.unit, p.barcode, p.default_quantity, p.package_unit
FROM inventory i
JOIN products p ON i.product_id = p.id
";
@@ -404,6 +404,14 @@ function addToInventory(PDO $db): void {
$stmt->execute([$unit, $quantity, $productId]);
}
// Update package info if conf
$packageUnit = $input['package_unit'] ?? null;
$packageSize = $input['package_size'] ?? null;
if ($packageUnit !== null) {
$stmt = $db->prepare("UPDATE products SET package_unit = ?, default_quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$packageUnit, $packageSize ?: 0, $productId]);
}
// Check if product already exists in this location
$stmt = $db->prepare("SELECT id, quantity FROM inventory WHERE product_id = ? AND location = ?");
$stmt->execute([$productId, $location]);
@@ -553,6 +561,12 @@ function updateInventory(PDO $db): void {
$stmt->execute([$input['unit'], $input['product_id']]);
}
// Update package info if provided
if (isset($input['package_unit']) && isset($input['product_id'])) {
$stmt = $db->prepare("UPDATE products SET package_unit = ?, default_quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->execute([$input['package_unit'], $input['package_size'] ?? 0, $input['product_id']]);
}
echo json_encode(['success' => true]);
}
@@ -611,7 +625,7 @@ function getStats(PDO $db): void {
// Expiring soonest (next 4 items to expire)
$expiring = $db->query("
SELECT i.*, p.name, p.brand, p.category, p.unit
SELECT i.*, p.name, p.brand, p.category, p.unit, p.default_quantity, p.package_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
@@ -620,7 +634,7 @@ function getStats(PDO $db): void {
// Expired
$expired = $db->query("
SELECT i.*, p.name, p.brand, p.category, p.unit
SELECT i.*, p.name, p.brand, p.category, p.unit, p.default_quantity, p.package_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
@@ -847,7 +861,7 @@ function geminiChat(PDO $db): void {
// Fetch inventory context
$stmt = $db->query("
SELECT p.name, p.brand, p.category, i.quantity, p.unit, i.location, i.expiry_date,
SELECT p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date,
CASE WHEN i.expiry_date IS NOT NULL THEN julianday(i.expiry_date) - julianday('now') ELSE 999 END AS days_left
FROM inventory i
JOIN products p ON p.id = i.product_id
@@ -861,6 +875,9 @@ function geminiChat(PDO $db): void {
$line = "- {$item['name']}";
if ($item['brand']) $line .= " ({$item['brand']})";
$line .= ": {$item['quantity']} {$item['unit']}";
if ($item['unit'] === 'conf' && !empty($item['package_unit']) && $item['default_quantity'] > 0) {
$line .= " (da {$item['default_quantity']} {$item['package_unit']} ciascuna)";
}
if ($item['expiry_date']) {
$daysLeft = intval($item['days_left']);
if ($daysLeft < 0) {
@@ -1004,7 +1021,7 @@ function generateRecipe(PDO $db): void {
// Fetch all inventory items with expiry info
$stmt = $db->query("
SELECT p.id AS product_id, p.name, p.brand, p.category, i.quantity, p.unit, i.location, i.expiry_date,
SELECT p.id AS product_id, p.name, p.brand, p.category, i.quantity, p.unit, p.default_quantity, p.package_unit, i.location, i.expiry_date,
CASE WHEN i.expiry_date IS NOT NULL THEN julianday(i.expiry_date) - julianday('now') ELSE 999 END AS days_left
FROM inventory i
JOIN products p ON p.id = i.product_id
@@ -1024,6 +1041,9 @@ function generateRecipe(PDO $db): void {
$line = "- {$item['name']}";
if ($item['brand']) $line .= " ({$item['brand']})";
$line .= ": {$item['quantity']} {$item['unit']}";
if ($item['unit'] === 'conf' && !empty($item['package_unit']) && $item['default_quantity'] > 0) {
$line .= " (da {$item['default_quantity']} {$item['package_unit']} ciascuna, totale: " . ($item['quantity'] * $item['default_quantity']) . " {$item['package_unit']})";
}
if ($item['expiry_date']) {
$daysLeft = intval($item['days_left']);
if ($daysLeft < 0) {
@@ -1210,6 +1230,7 @@ PROMPT;
$ing['inventory_unit'] = $bestMatch['unit'];
$ing['inventory_qty'] = (float)$bestMatch['quantity'];
$ing['default_quantity'] = (float)($bestMatch['default_quantity'] ?? 0);
$ing['package_unit'] = $bestMatch['package_unit'] ?? '';
$ing['available_qty'] = $bestMatch['quantity'] . ' ' . $bestMatch['unit'];
if (!empty($bestMatch['brand'])) {
$ing['brand'] = $bestMatch['brand'];
+39
View File
@@ -1919,6 +1919,45 @@ body {
border-radius: var(--radius-sm);
}
/* ===== CONF PACKAGE SIZE ROW ===== */
.conf-size-row {
margin-top: 8px;
padding: 10px 12px;
background: #f0f9ff;
border: 1.5px solid #bae6fd;
border-radius: var(--radius-sm);
}
.conf-size-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 6px;
color: #0369a1;
}
.conf-size-inputs {
display: flex;
gap: 8px;
align-items: center;
}
.conf-size-input {
flex: 1;
max-width: 120px;
}
.conf-size-unit {
width: 65px;
min-width: 65px;
}
.conf-size-info {
font-size: 0.75em;
color: var(--text-light);
font-weight: 500;
}
/* ===== EXPIRY INPUT + CAMERA ROW ===== */
.expiry-input-row {
display: flex;
+102 -23
View File
@@ -572,7 +572,7 @@ 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);
const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
return `
<div class="alert-item alert-item-clickable" onclick="showAlertItemDetail(${item.id}, ${item.product_id})">
<div class="alert-item-info">
@@ -602,7 +602,7 @@ 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);
const qtyDisplayExp = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
return `
<div class="alert-item expired-item alert-item-clickable" onclick="showAlertItemDetail(${item.id}, ${item.product_id})">
<div class="alert-item-info">
@@ -676,7 +676,7 @@ async function loadReviewItems() {
section.style.display = 'block';
list.innerHTML = suspicious.map(item => {
const catIcon = CATEGORY_ICONS[mapToLocalCategory(item.category, item.name)] || '📦';
const qtyDisplay = formatQuantity(item.quantity, item.unit);
const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
const locInfo = LOCATIONS[item.location] || { icon: '📦', label: item.location };
const t = QTY_THRESHOLDS[item.unit] || QTY_THRESHOLDS['pz'];
const isTooSmall = parseFloat(item.quantity) < t.min;
@@ -763,7 +763,7 @@ function renderDashItem(item) {
const days = daysUntilExpiry(item.expiry_date);
const isExpired = days < 0;
const isExpiring = !isExpired && days <= 7;
const qtyDisplay = formatQuantity(item.quantity, item.unit);
const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
let expiryLabel = '';
if (item.expiry_date) {
@@ -806,15 +806,21 @@ function showAlertItemDetail(inventoryId, productId) {
});
}
function formatQuantity(qty, unit) {
function formatQuantity(qty, unit, defaultQty, packageUnit) {
if (!qty && qty !== 0) return '';
const n = parseFloat(qty);
const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' };
const label = unitLabels[unit] || unit || 'pz';
if (n === Math.floor(n)) return `${Math.floor(n)} ${label}`;
// For pz/conf show whole number
if (unit === 'pz' || unit === 'conf') return `${Math.round(n)} ${label}`;
return `${n.toFixed(1)} ${label}`;
let result;
if (n === Math.floor(n)) result = `${Math.floor(n)} ${label}`;
else if (unit === 'pz' || unit === 'conf') result = `${Math.round(n)} ${label}`;
else result = `${n.toFixed(1)} ${label}`;
// Add package info for conf
if (unit === 'conf' && packageUnit && defaultQty > 0) {
const pkgLabel = unitLabels[packageUnit] || packageUnit;
result += ` <span class="conf-size-info">(da ${defaultQty}${pkgLabel})</span>`;
}
return result;
}
// Show package fraction: only ¼, ½, ¾ when there's a partial package.
@@ -856,7 +862,7 @@ function renderInventoryItem(item) {
const days = daysUntilExpiry(item.expiry_date);
const isExpired = days < 0;
const isExpiring = !isExpired && days <= 7;
const qtyDisplay = formatQuantity(item.quantity, item.unit);
const qtyDisplay = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit);
const pkgFrac = formatPackageFraction(item.quantity, item.default_quantity);
let expiryBadge = '';
@@ -951,7 +957,7 @@ function showItemDetail(inventoryId, productId) {
</div>
<div class="modal-detail-row">
<span class="modal-detail-label">📦 Quantità</span>
<span class="modal-detail-value">${item.quantity} ${item.unit}</span>
<span class="modal-detail-value">${formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit)}</span>
</div>
${item.expiry_date ? `
<div class="modal-detail-row">
@@ -1032,6 +1038,10 @@ function editInventoryItem(id) {
return;
}
const isConf = (item.unit || 'pz') === 'conf';
const confSizeVal = (isConf && item.default_quantity > 0) ? item.default_quantity : '';
const confUnitVal = (isConf && item.package_unit) ? item.package_unit : 'g';
// Rebuild modal content for editing (don't close and reopen - just replace content)
document.getElementById('modal-content').innerHTML = `
<div class="modal-header">
@@ -1049,10 +1059,19 @@ function editInventoryItem(id) {
</div>
<div class="form-group">
<label>📏 Unità di misura</label>
<select id="edit-unit" class="form-input">
<select id="edit-unit" class="form-input" onchange="onEditUnitChange()">
${['pz','g','kg','ml','l','conf'].map(u => `<option value="${u}" ${(item.unit||'pz') === u ? 'selected' : ''}>${u === 'pz' ? 'pz (pezzi)' : u === 'g' ? 'g (grammi)' : u === 'kg' ? 'kg (chilogrammi)' : u === 'ml' ? 'ml (millilitri)' : u === 'l' ? 'L (litri)' : u === 'conf' ? 'conf (confezioni)' : u}</option>`).join('')}
</select>
</div>
<div class="form-group" id="edit-conf-size-group" style="display:${isConf ? 'block' : 'none'}">
<label>📦 Ogni confezione contiene:</label>
<div class="conf-size-inputs">
<input type="number" id="edit-conf-size" class="form-input conf-size-input" min="1" step="any" value="${confSizeVal}" placeholder="es. 300">
<select id="edit-conf-unit" class="form-input conf-size-unit">
${['g','kg','ml','l'].map(u => `<option value="${u}" ${confUnitVal === u ? 'selected' : ''}>${u === 'l' ? 'L' : u}</option>`).join('')}
</select>
</div>
</div>
<div class="form-group">
<label>📍 Posizione</label>
<div class="location-selector">
@@ -1073,6 +1092,12 @@ function editInventoryItem(id) {
document.getElementById('modal-overlay').style.display = 'flex';
}
function onEditUnitChange() {
const unit = document.getElementById('edit-unit').value;
const confGroup = document.getElementById('edit-conf-size-group');
if (confGroup) confGroup.style.display = unit === 'conf' ? 'block' : 'none';
}
async function submitEditInventory(e, id, productId) {
e.preventDefault();
const qty = parseFloat(document.getElementById('edit-qty').value);
@@ -1080,7 +1105,19 @@ async function submitEditInventory(e, id, productId) {
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, unit, product_id: productId });
const payload = { id, quantity: qty, location: loc, expiry_date: expiry, unit, product_id: productId };
// Add package info if conf
if (unit === 'conf') {
payload.package_unit = document.getElementById('edit-conf-unit')?.value || '';
payload.package_size = parseFloat(document.getElementById('edit-conf-size')?.value) || 0;
} else {
// Clear package info if not conf
payload.package_unit = '';
payload.package_size = 0;
}
await api('inventory_update', {}, 'POST', payload);
closeModal();
showToast('Aggiornato!', 'success');
refreshCurrentPage();
@@ -1608,17 +1645,25 @@ function onCategoryChange(fromAutoDetect = false) {
}
}
function onPfUnitChange() {
const unit = document.getElementById('pf-unit').value;
const confRow = document.getElementById('pf-conf-size-row');
if (confRow) confRow.style.display = unit === 'conf' ? 'block' : 'none';
}
async function submitProduct(e) {
e.preventDefault();
showLoading(true);
const pfUnit = document.getElementById('pf-unit').value;
const productData = {
id: document.getElementById('pf-id').value || null,
name: document.getElementById('pf-name').value,
brand: document.getElementById('pf-brand').value,
category: document.getElementById('pf-category').value,
unit: document.getElementById('pf-unit').value,
default_quantity: parseFloat(document.getElementById('pf-defqty').value) || 1,
unit: pfUnit,
default_quantity: pfUnit === 'conf' ? (parseFloat(document.getElementById('pf-conf-size')?.value) || 1) : (parseFloat(document.getElementById('pf-defqty').value) || 1),
package_unit: pfUnit === 'conf' ? (document.getElementById('pf-conf-unit')?.value || '') : '',
notes: document.getElementById('pf-notes').value,
barcode: document.getElementById('pf-barcode').value || null,
image_url: document.getElementById('pf-image').value || '',
@@ -1811,9 +1856,10 @@ function showProductAction() {
let totalQty = 0;
const unit = inventoryItems[0].unit || 'pz';
const defQty = inventoryItems[0].default_quantity || 0;
const pkgUnit = inventoryItems[0].package_unit || '';
const invHtml = inventoryItems.map(inv => {
const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location };
const qtyStr = formatQuantity(inv.quantity, inv.unit);
const qtyStr = formatQuantity(inv.quantity, inv.unit, inv.default_quantity, inv.package_unit);
const pkgF = formatPackageFraction(inv.quantity, inv.default_quantity);
totalQty += parseFloat(inv.quantity);
let expiryStr = '';
@@ -1827,7 +1873,7 @@ function showProductAction() {
return `<div class="inv-status-item"><span>${locInfo.icon} ${locInfo.label}${expiryStr}</span><span class="inv-status-qty">${qtyStr}${pkgF ? ' ' + pkgF : ''}</span></div>`;
}).join('');
const totalStr = formatQuantity(totalQty, unit);
const totalStr = formatQuantity(totalQty, unit, defQty, pkgUnit);
const totalFrac = formatPackageFraction(totalQty, defQty);
statusBar.innerHTML = `
@@ -1894,11 +1940,13 @@ function showThrowForm() {
const totalQty = items.reduce((sum, i) => sum + parseFloat(i.quantity), 0);
const unit = items[0].unit || 'pz';
const qtyDisplay = formatQuantity(totalQty, unit);
const defQty = items[0].default_quantity || 0;
const pkgUnit = items[0].package_unit || '';
const qtyDisplay = formatQuantity(totalQty, unit, defQty, pkgUnit);
let locOptionsHtml = items.map(inv => {
const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location };
return `<div class="inv-status-item"><span>${locInfo.icon} ${locInfo.label}</span><span class="inv-status-qty">${formatQuantity(inv.quantity, inv.unit)}</span></div>`;
return `<div class="inv-status-item"><span>${locInfo.icon} ${locInfo.label}</span><span class="inv-status-qty">${formatQuantity(inv.quantity, inv.unit, inv.default_quantity, inv.package_unit)}</span></div>`;
}).join('');
document.getElementById('modal-content').innerHTML = `
@@ -1929,7 +1977,7 @@ function showThrowForm() {
<div class="location-selector" id="throw-location-selector">
${items.map((inv, idx) => {
const locInfo = LOCATIONS[inv.location] || { icon: '📦', label: inv.location };
return `<button type="button" class="loc-btn ${idx === 0 ? 'active' : ''}" onclick="selectThrowLocation(this, '${inv.location}')">${locInfo.icon} ${locInfo.label} (${formatQuantity(inv.quantity, inv.unit)})</button>`;
return `<button type="button" class="loc-btn ${idx === 0 ? 'active' : ''}" onclick="selectThrowLocation(this, '${inv.location}')">${locInfo.icon} ${locInfo.label} (${formatQuantity(inv.quantity, inv.unit, inv.default_quantity, inv.package_unit)})</button>`;
}).join('')}
</div>
<input type="hidden" id="throw-location" value="${items[0].location}">
@@ -2066,9 +2114,22 @@ function showAddForm() {
const unitSelect = document.getElementById('add-unit');
unitSelect.value = unit;
document.getElementById('add-quantity').value = currentProduct.default_quantity || 1;
document.getElementById('add-quantity').value = unit === 'conf' ? (currentProduct.last_qty || 1) : (currentProduct.default_quantity || 1);
document.getElementById('add-quantity').dataset.manuallySet = 'false';
// Show/hide conf size row and pre-fill
const confRow = document.getElementById('add-conf-size-row');
if (confRow) {
confRow.style.display = unit === 'conf' ? 'block' : 'none';
if (unit === 'conf' && currentProduct.package_unit && currentProduct.default_quantity > 0) {
document.getElementById('add-conf-size').value = currentProduct.default_quantity;
document.getElementById('add-conf-unit').value = currentProduct.package_unit;
} else if (unit === 'conf') {
document.getElementById('add-conf-size').value = '';
document.getElementById('add-conf-unit').value = 'g';
}
}
// Track manual edits to quantity in add form
const addQtyInput = document.getElementById('add-quantity');
addQtyInput.removeEventListener('input', markAddQtyManuallySet);
@@ -2131,10 +2192,26 @@ function showAddForm() {
function onAddUnitChange() {
updateAddQtyStep();
// If switching units, suggest a sensible quantity
// BUT only if the user hasn't manually changed the quantity in this form
const unit = document.getElementById('add-unit').value;
const qtyInput = document.getElementById('add-quantity');
// Show/hide conf size row
const confRow = document.getElementById('add-conf-size-row');
if (confRow) {
confRow.style.display = unit === 'conf' ? 'block' : 'none';
// Pre-fill from currentProduct if available
if (unit === 'conf' && currentProduct) {
const sizeInput = document.getElementById('add-conf-size');
const unitSelect = document.getElementById('add-conf-unit');
if (currentProduct.package_unit && currentProduct.default_quantity > 1) {
sizeInput.value = currentProduct.default_quantity;
unitSelect.value = currentProduct.package_unit;
}
}
}
// If switching units, suggest a sensible quantity
// BUT only if the user hasn't manually changed the quantity in this form
if (qtyInput.dataset.manuallySet === 'true') return; // User already edited qty, don't overwrite
const currentQty = parseFloat(qtyInput.value) || 1;
@@ -2267,6 +2344,8 @@ async function submitAdd(e) {
location: document.getElementById('add-location').value,
expiry_date: document.getElementById('add-expiry').value || null,
unit: selectedUnit !== productUnit ? selectedUnit : null,
package_unit: selectedUnit === 'conf' ? (document.getElementById('add-conf-unit')?.value || null) : null,
package_size: selectedUnit === 'conf' ? (parseFloat(document.getElementById('add-conf-size')?.value) || null) : null,
});
showLoading(false);
BIN
View File
Binary file not shown.
+25 -1
View File
@@ -192,6 +192,18 @@
<option value="l">L</option>
</select>
</div>
<div id="add-conf-size-row" class="conf-size-row" style="display:none">
<label class="conf-size-label">📦 Ogni confezione contiene:</label>
<div class="conf-size-inputs">
<input type="number" id="add-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300">
<select id="add-conf-unit" class="form-input conf-size-unit">
<option value="g">g</option>
<option value="kg">kg</option>
<option value="ml">ml</option>
<option value="l">L</option>
</select>
</div>
</div>
<div id="add-weight-info" class="form-hint" style="display:none"></div>
</div>
<div class="form-group" id="add-expiry-section">
@@ -380,7 +392,7 @@
<div class="form-row">
<div class="form-group flex-1">
<label>📏 Unità di misura</label>
<select id="pf-unit" class="form-input">
<select id="pf-unit" class="form-input" onchange="onPfUnitChange()">
<option value="pz">Pezzi</option>
<option value="kg">Kg</option>
<option value="g">Grammi</option>
@@ -394,6 +406,18 @@
<input type="number" id="pf-defqty" class="form-input" value="1" min="0.1" step="any">
</div>
</div>
<div id="pf-conf-size-row" class="conf-size-row" style="display:none">
<label class="conf-size-label">📦 Ogni confezione contiene:</label>
<div class="conf-size-inputs">
<input type="number" id="pf-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300">
<select id="pf-conf-unit" class="form-input conf-size-unit">
<option value="g">g</option>
<option value="kg">kg</option>
<option value="ml">ml</option>
<option value="l">L</option>
</select>
</div>
</div>
<div class="form-group">
<label>📝 Note</label>
<textarea id="pf-notes" class="form-input" rows="2" placeholder="Es: senza lattosio, bio, conservare in frigo dopo apertura..."></textarea>