Fix: camera scadenza più piccola con zoom 2x, quantità non si resetta più, nomi prodotti in lingua corretta
- Ridotto il frame della camera scadenza a 180px con zoom 2x e crop centrale - Fix: premere 'Appena comprato' / 'Ce l'avevo già' non modifica più la quantità - Fix: nomi prodotti in script non-latino vengono sostituiti con alternative leggibili - Corretto nome prodotto 'Celozrnný toastový chléb' → 'Sandwich American Style' - Migliorato ordine fallback nomi: product_name_it → generic_name_it → product_name
This commit is contained in:
+22
-2
@@ -141,17 +141,37 @@ function lookupBarcode(): void {
|
||||
$p = $data['product'];
|
||||
|
||||
// Prefer Italian name, fall back to generic
|
||||
// Also request localized name via abbreviated_product_name
|
||||
$name = '';
|
||||
if (!empty($p['product_name_it'])) {
|
||||
$name = $p['product_name_it'];
|
||||
} elseif (!empty($p['product_name'])) {
|
||||
$name = $p['product_name'];
|
||||
} elseif (!empty($p['generic_name_it'])) {
|
||||
$name = $p['generic_name_it'];
|
||||
} elseif (!empty($p['product_name'])) {
|
||||
$name = $p['product_name'];
|
||||
} elseif (!empty($p['generic_name'])) {
|
||||
$name = $p['generic_name'];
|
||||
}
|
||||
|
||||
// If the name looks like it's in a non-Latin script (Arabic, Chinese, Thai, etc.)
|
||||
// try to use a fallback from brands + generic category
|
||||
if (!empty($name) && preg_match('/[\x{0600}-\x{06FF}\x{0E00}-\x{0E7F}\x{4E00}-\x{9FFF}\x{3040}-\x{30FF}\x{AC00}-\x{D7AF}\x{0400}-\x{04FF}]/u', $name)) {
|
||||
// Try other name fields that might be in Latin script
|
||||
$latinName = '';
|
||||
foreach (['generic_name_it', 'generic_name', 'product_name_it', 'product_name'] as $field) {
|
||||
if (!empty($p[$field]) && !preg_match('/[\x{0600}-\x{06FF}\x{0E00}-\x{0E7F}\x{4E00}-\x{9FFF}\x{3040}-\x{30FF}\x{AC00}-\x{D7AF}\x{0400}-\x{04FF}]/u', $p[$field])) {
|
||||
$latinName = $p[$field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If still no Latin name, construct from brand + category
|
||||
if (empty($latinName)) {
|
||||
$brand = $p['brands'] ?? '';
|
||||
$latinName = !empty($brand) ? $brand : 'Prodotto sconosciuto';
|
||||
}
|
||||
$name = $latinName;
|
||||
}
|
||||
|
||||
// Get Italian ingredients, fall back to generic
|
||||
$ingredients = '';
|
||||
if (!empty($p['ingredients_text_it'])) {
|
||||
|
||||
+76
-28
@@ -490,7 +490,7 @@ function editInventoryItem(id) {
|
||||
<label>📦 Quantità</label>
|
||||
<div class="qty-control">
|
||||
<button type="button" class="qty-btn" onclick="adjustQty('edit-qty', -1)">−</button>
|
||||
<input type="number" id="edit-qty" value="${item.quantity}" min="0" step="0.5" class="qty-input">
|
||||
<input type="number" id="edit-qty" value="${item.quantity}" min="0" step="any" class="qty-input">
|
||||
<button type="button" class="qty-btn" onclick="adjustQty('edit-qty', 1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -746,6 +746,15 @@ function startManualEntry(barcode = '') {
|
||||
document.getElementById('pf-image-preview').style.display = 'none';
|
||||
document.getElementById('product-form-title').textContent = 'Nuovo Prodotto';
|
||||
|
||||
// Reset manual-edit tracking flags
|
||||
document.getElementById('pf-category').dataset.manuallySet = 'false';
|
||||
document.getElementById('pf-defqty').dataset.manuallySet = 'false';
|
||||
|
||||
// Track if user manually changes the quantity field
|
||||
const qtyInput = document.getElementById('pf-defqty');
|
||||
qtyInput.removeEventListener('input', markQtyManuallySet);
|
||||
qtyInput.addEventListener('input', markQtyManuallySet);
|
||||
|
||||
// Auto-detect name → category when typing
|
||||
const nameInput = document.getElementById('pf-name');
|
||||
nameInput.removeEventListener('input', autoDetectCategory);
|
||||
@@ -754,6 +763,10 @@ function startManualEntry(barcode = '') {
|
||||
showPage('product-form');
|
||||
}
|
||||
|
||||
function markQtyManuallySet() {
|
||||
document.getElementById('pf-defqty').dataset.manuallySet = 'true';
|
||||
}
|
||||
|
||||
function autoDetectCategory() {
|
||||
const name = document.getElementById('pf-name').value.toLowerCase();
|
||||
if (name.length < 3) return;
|
||||
@@ -796,23 +809,26 @@ function autoDetectCategory() {
|
||||
for (const [keyword, cat] of Object.entries(keyword2cat)) {
|
||||
if (name.includes(keyword)) {
|
||||
catSelect.value = cat;
|
||||
onCategoryChange();
|
||||
onCategoryChange(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCategoryChange() {
|
||||
function onCategoryChange(fromAutoDetect = false) {
|
||||
const cat = document.getElementById('pf-category').value;
|
||||
const unitSelect = document.getElementById('pf-unit');
|
||||
const qtyInput = document.getElementById('pf-defqty');
|
||||
|
||||
// Mark as manually set if triggered by user click
|
||||
if (event && event.isTrusted) {
|
||||
document.getElementById('pf-category').dataset.manuallySet = 'true';
|
||||
// If user manually changed category via dropdown, don't auto-fill qty/unit
|
||||
if (!fromAutoDetect) {
|
||||
// Mark qty as "set" so future auto-detects won't overwrite either
|
||||
qtyInput.dataset.manuallySet = 'true';
|
||||
return;
|
||||
}
|
||||
|
||||
// Suggest default unit/qty based on category
|
||||
// Auto-detect from name: suggest default unit/qty based on category
|
||||
// BUT only if user hasn't manually changed the quantity field
|
||||
const catDefaults = {
|
||||
'latticini': { unit: 'pz', qty: 1 },
|
||||
'carne': { unit: 'g', qty: 500 },
|
||||
@@ -832,10 +848,13 @@ function onCategoryChange() {
|
||||
};
|
||||
|
||||
if (catDefaults[cat]) {
|
||||
// Only auto-fill unit/qty if user hasn't manually touched them
|
||||
if (qtyInput.dataset.manuallySet !== 'true') {
|
||||
unitSelect.value = catDefaults[cat].unit;
|
||||
qtyInput.value = catDefaults[cat].qty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function submitProduct(e) {
|
||||
e.preventDefault();
|
||||
@@ -1002,6 +1021,12 @@ function showAddForm() {
|
||||
unitSelect.value = unit;
|
||||
|
||||
document.getElementById('add-quantity').value = currentProduct.default_quantity || 1;
|
||||
document.getElementById('add-quantity').dataset.manuallySet = 'false';
|
||||
|
||||
// Track manual edits to quantity in add form
|
||||
const addQtyInput = document.getElementById('add-quantity');
|
||||
addQtyInput.removeEventListener('input', markAddQtyManuallySet);
|
||||
addQtyInput.addEventListener('input', markAddQtyManuallySet);
|
||||
|
||||
// Show weight info if product has it
|
||||
const weightInfoEl = document.getElementById('add-weight-info');
|
||||
@@ -1061,8 +1086,11 @@ 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');
|
||||
if (qtyInput.dataset.manuallySet === 'true') return; // User already edited qty, don't overwrite
|
||||
|
||||
const currentQty = parseFloat(qtyInput.value) || 1;
|
||||
|
||||
// Convert between related units if logical
|
||||
@@ -1077,28 +1105,38 @@ function onAddUnitChange() {
|
||||
function updateAddQtyStep() {
|
||||
const qtyInput = document.getElementById('add-quantity');
|
||||
const unit = document.getElementById('add-unit').value;
|
||||
qtyInput.step = 'any';
|
||||
if (unit === 'g' || unit === 'ml') {
|
||||
qtyInput.step = '25';
|
||||
qtyInput.min = '1';
|
||||
} else if (unit === 'kg' || unit === 'l') {
|
||||
qtyInput.step = '0.25';
|
||||
qtyInput.min = '0.1';
|
||||
} else {
|
||||
qtyInput.step = '1';
|
||||
qtyInput.min = '1';
|
||||
}
|
||||
}
|
||||
|
||||
function markAddQtyManuallySet() {
|
||||
document.getElementById('add-quantity').dataset.manuallySet = 'true';
|
||||
}
|
||||
|
||||
function adjustAddQty(delta) {
|
||||
const qtyInput = document.getElementById('add-quantity');
|
||||
qtyInput.dataset.manuallySet = 'true'; // +/- buttons count as manual edit
|
||||
const unit = document.getElementById('add-unit').value;
|
||||
let step;
|
||||
if (unit === 'g' || unit === 'ml') step = 25;
|
||||
else if (unit === 'kg' || unit === 'l') step = 0.25;
|
||||
else step = 1;
|
||||
let val = parseFloat(qtyInput.value) || 0;
|
||||
let step;
|
||||
if (unit === 'kg' || unit === 'l') {
|
||||
step = val < 1 ? 0.1 : 0.5;
|
||||
} else if (unit === 'g' || unit === 'ml') {
|
||||
step = val < 50 ? 1 : (val < 500 ? 10 : 50);
|
||||
} else {
|
||||
step = 1;
|
||||
}
|
||||
val = Math.max(parseFloat(qtyInput.min) || 0.1, val + delta * step);
|
||||
qtyInput.value = Math.round(val * 100) / 100;
|
||||
// Round nicely
|
||||
if (step >= 1) val = Math.round(val);
|
||||
else val = Math.round(val * 10) / 10;
|
||||
qtyInput.value = val;
|
||||
}
|
||||
|
||||
function selectPurchaseType(btn, type, estimatedDate, estimateLabel) {
|
||||
@@ -1107,6 +1145,9 @@ function selectPurchaseType(btn, type, estimatedDate, estimateLabel) {
|
||||
|
||||
const detailDiv = document.getElementById('expiry-detail');
|
||||
|
||||
// Save current quantity before switching, so we can preserve it
|
||||
const currentQty = document.getElementById('add-quantity').value;
|
||||
|
||||
if (type === 'new') {
|
||||
detailDiv.innerHTML = `
|
||||
<div class="expiry-estimate">
|
||||
@@ -1119,6 +1160,8 @@ function selectPurchaseType(btn, type, estimatedDate, estimateLabel) {
|
||||
</div>
|
||||
<p class="form-hint">📝 Puoi modificare la data o scansionarla con la fotocamera</p>
|
||||
`;
|
||||
// Restore quantity - switching purchase type should NOT change it
|
||||
document.getElementById('add-quantity').value = currentQty;
|
||||
} else {
|
||||
detailDiv.innerHTML = `
|
||||
<div class="form-group">
|
||||
@@ -1134,14 +1177,13 @@ function selectPurchaseType(btn, type, estimatedDate, estimateLabel) {
|
||||
<p class="form-hint" style="margin-bottom:6px">Quanto è rimasto approssimativamente?</p>
|
||||
<div class="remaining-options">
|
||||
<button type="button" class="remaining-btn" onclick="setRemainingPct(1)">🟢 Pieno</button>
|
||||
<button type="button" class="remaining-btn active" onclick="setRemainingPct(0.75)">🟡 ¾</button>
|
||||
<button type="button" class="remaining-btn" onclick="setRemainingPct(0.75)">🟡 ¾</button>
|
||||
<button type="button" class="remaining-btn" onclick="setRemainingPct(0.5)">🟠 Metà</button>
|
||||
<button type="button" class="remaining-btn" onclick="setRemainingPct(0.25)">🔴 ¼</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// Pre-set to 75%
|
||||
setRemainingPct(0.75);
|
||||
// DON'T auto-set remaining percentage - keep the quantity the user already entered
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1526,16 +1568,17 @@ async function scanExpiryWithAI() {
|
||||
<button class="modal-close" onclick="closeExpiryScanner()">✕</button>
|
||||
</div>
|
||||
<div class="expiry-scanner">
|
||||
<div id="expiry-cam-container">
|
||||
<video id="expiry-video" autoplay playsinline style="width:100%;border-radius:10px"></video>
|
||||
<div id="expiry-cam-container" style="height:180px;overflow:hidden;border-radius:10px;position:relative">
|
||||
<video id="expiry-video" autoplay playsinline style="width:100%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(2);transform-origin:center center"></video>
|
||||
<canvas id="expiry-canvas" style="display:none"></canvas>
|
||||
<div style="position:absolute;inset:0;border:2px dashed rgba(255,255,255,0.5);border-radius:10px;pointer-events:none"></div>
|
||||
</div>
|
||||
<div id="expiry-preview-container" style="display:none">
|
||||
<img id="expiry-preview-img" src="" alt="" style="width:100%;border-radius:10px">
|
||||
<div id="expiry-preview-container" style="display:none;height:180px;overflow:hidden;border-radius:10px">
|
||||
<img id="expiry-preview-img" src="" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:10px">
|
||||
</div>
|
||||
<p class="form-hint" style="text-align:center;margin:8px 0">Inquadra la data di scadenza stampata sul prodotto</p>
|
||||
<div id="expiry-scan-status" style="display:none;text-align:center;padding:12px">
|
||||
<div class="loading-spinner" style="margin:0 auto 8px"></div>
|
||||
<p class="form-hint" style="text-align:center;margin:6px 0;font-size:0.8rem">Inquadra la data di scadenza stampata sul prodotto</p>
|
||||
<div id="expiry-scan-status" style="display:none;text-align:center;padding:8px">
|
||||
<div class="loading-spinner" style="margin:0 auto 6px"></div>
|
||||
<p>🤖 Analisi AI in corso...</p>
|
||||
</div>
|
||||
<div class="expiry-scanner-actions">
|
||||
@@ -1575,10 +1618,15 @@ function captureExpiry() {
|
||||
const canvas = document.getElementById('expiry-canvas');
|
||||
const img = document.getElementById('expiry-preview-img');
|
||||
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
// Crop to center 50% (matching the 2x zoom view) for better AI accuracy
|
||||
const sw = video.videoWidth / 2;
|
||||
const sh = video.videoHeight / 2;
|
||||
const sx = (video.videoWidth - sw) / 2;
|
||||
const sy = (video.videoHeight - sh) / 2;
|
||||
canvas.width = sw;
|
||||
canvas.height = sh;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(video, 0, 0);
|
||||
ctx.drawImage(video, sx, sy, sw, sh, 0, 0, sw, sh);
|
||||
|
||||
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
||||
img.src = dataUrl;
|
||||
|
||||
+4
-4
@@ -167,7 +167,7 @@
|
||||
<div class="qty-unit-row">
|
||||
<div class="qty-control flex-1">
|
||||
<button type="button" class="qty-btn" onclick="adjustAddQty(-1)">−</button>
|
||||
<input type="number" id="add-quantity" value="1" min="0.1" step="0.5" class="qty-input">
|
||||
<input type="number" id="add-quantity" value="1" min="0.1" step="any" class="qty-input">
|
||||
<button type="button" class="qty-btn" onclick="adjustAddQty(1)">+</button>
|
||||
</div>
|
||||
<select id="add-unit" class="form-input unit-select" onchange="onAddUnitChange()">
|
||||
@@ -217,7 +217,7 @@
|
||||
<p>Oppure specifica la quantità usata:</p>
|
||||
<div class="qty-control">
|
||||
<button type="button" class="qty-btn" onclick="adjustQty('use-quantity', -0.5)">−</button>
|
||||
<input type="number" id="use-quantity" value="1" min="0.1" step="0.5" class="qty-input">
|
||||
<input type="number" id="use-quantity" value="1" min="0.1" step="any" class="qty-input">
|
||||
<button type="button" class="qty-btn" onclick="adjustQty('use-quantity', 0.5)">+</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-large btn-warning full-width mt-2">📤 Usa questa quantità</button>
|
||||
@@ -344,7 +344,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>📂 Categoria</label>
|
||||
<select id="pf-category" class="form-input" onchange="onCategoryChange()">
|
||||
<select id="pf-category" class="form-input" onchange="onCategoryChange(false)">
|
||||
<option value="">-- Seleziona --</option>
|
||||
<option value="latticini">🥛 Latticini</option>
|
||||
<option value="carne">🥩 Carne</option>
|
||||
@@ -378,7 +378,7 @@
|
||||
</div>
|
||||
<div class="form-group flex-1">
|
||||
<label>🔢 Quantità default</label>
|
||||
<input type="number" id="pf-defqty" class="form-input" value="1" min="0.1" step="0.5">
|
||||
<input type="number" id="pf-defqty" class="form-input" value="1" min="0.1" step="any">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
Reference in New Issue
Block a user