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:
dadaloop82
2026-03-10 11:20:43 +00:00
parent 70ee253a07
commit 706c8aeae0
3 changed files with 104 additions and 36 deletions
+22 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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">