diff --git a/api/index.php b/api/index.php
index a239ce4..db28f13 100644
--- a/api/index.php
+++ b/api/index.php
@@ -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'])) {
diff --git a/assets/js/app.js b/assets/js/app.js
index 216b45c..83ee710 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -490,7 +490,7 @@ function editInventoryItem(id) {
-
+
@@ -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,8 +848,11 @@ function onCategoryChange() {
};
if (catDefaults[cat]) {
- unitSelect.value = catDefaults[cat].unit;
- qtyInput.value = catDefaults[cat].qty;
+ // 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;
+ }
}
}
@@ -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 = `
@@ -1119,6 +1160,8 @@ function selectPurchaseType(btn, type, estimatedDate, estimateLabel) {
📝 Puoi modificare la data o scansionarla con la fotocamera
`;
+ // Restore quantity - switching purchase type should NOT change it
+ document.getElementById('add-quantity').value = currentQty;
} else {
detailDiv.innerHTML = `
`;
- // 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() {
-
-
+
-
-
![]()
+
+
-
Inquadra la data di scadenza stampata sul prodotto
-
-
+
Inquadra la data di scadenza stampata sul prodotto
+
+
🤖 Analisi AI in corso...
@@ -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;
diff --git a/index.html b/index.html
index 4485508..515f8cf 100644
--- a/index.html
+++ b/index.html
@@ -167,7 +167,7 @@
-
-
+