function ItemDetailsPage({ initialBarcode = "" }) { const [input, setInput] = React.useState(initialBarcode); const [barcode, setBarcode] = React.useState(initialBarcode); const [product, setProduct] = React.useState(null); const [similar, setSimilar] = React.useState([]); const [loading, setLoading] = React.useState(false); const [loadingSimilar, setLoadingSimilar] = React.useState(false); const [error, setError] = React.useState(null); const [scanning, setScanning] = React.useState(false); const [debugProduct, setDebugProduct] = React.useState(null); const videoRef = React.useRef(null); const readerRef = React.useRef(null); React.useEffect(() => { if (debugProduct) { document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = ""; }; } }, [debugProduct]); function openDebug() { setDebugProduct(product._raw || product); } // Fetch product whenever barcode changes React.useEffect(() => { if (!barcode) return; window.location.hash = `#/item/${barcode}`; setProduct(null); setSimilar([]); setError(null); setLoading(true); fetch(`/api/search-off?barcode=${encodeURIComponent(barcode)}`) .then((r) => r.json()) .then((d) => { const hit = d.results?.[0]; if (hit) { setProduct({ ...hit, off_found: true, _raw: d.raw || null }); } else { setError("Prodotto non trovato su OpenFoodFacts."); } }) .catch(() => setError("Errore di rete.")) .finally(() => setLoading(false)); }, [barcode]); // Fetch similar products after product loads React.useEffect(() => { const bc = product?.barcode; if (!bc) return; setLoadingSimilar(true); fetch(`/api/similar/${encodeURIComponent(bc)}`) .then((r) => r.json()) .then((d) => setSimilar(d.results || [])) .catch(() => {}) .finally(() => setLoadingSimilar(false)); }, [product?.barcode]); // Start/stop camera with ZXing React.useEffect(() => { if (!scanning) return; if (!videoRef.current) return; const reader = new ZXing.BrowserMultiFormatReader(); readerRef.current = reader; reader .decodeFromVideoDevice(undefined, videoRef.current, (result) => { if (!result) return; const text = result.getText(); reader.reset(); readerRef.current = null; setScanning(false); setInput(text); setBarcode(text); }) .catch(() => setScanning(false)); return () => { reader.reset(); readerRef.current = null; }; }, [scanning]); function stopScan() { readerRef.current?.reset(); readerRef.current = null; setScanning(false); } function handleSubmit(e) { e.preventDefault(); const cleaned = input.trim(); if (cleaned) setBarcode(cleaned); } function loadProduct(p) { setInput(p.barcode); setBarcode(p.barcode); window.scrollTo({ top: 0, behavior: "smooth" }); } return (
{/* ── Barcode input ─────────────────────────────────────────── */}
setInput(e.target.value)} placeholder="Codice a barre…" className="flex-1 px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#2d7a3a]/30 focus:border-[#2d7a3a]" />
{/* Camera viewfinder */} {scanning && (
{/* ── Loading ───────────────────────────────────────────────── */} {loading && (
)} {/* ── Not found ─────────────────────────────────────────────── */} {error && !loading && (
{error}
)} {/* ── Italian Mediterranean score ───────────────────────────── */} {product && !loading && product.mediterranean_score && ( )} {/* ── Product card + nutrition ──────────────────────────────── */} {product && !loading && (
{/* Debug button */} {/* Header row */}
{product.product_name} { e.target.onerror = null; e.target.src = PLACEHOLDER_IMG; }} className="w-20 h-20 object-contain rounded-lg border border-gray-100 bg-white flex-shrink-0" />
{product.product_name || "—"}
{product.brands && (
{product.brands}
)} {product.quantity && (
{product.quantity}
)}
novaColor(parseInt(g))} title="NOVA" />
{/* Full nutrition panel */}
)} {/* ── Similar products ──────────────────────────────────────── */} {(loadingSimilar || similar.length > 0) && (
Prodotti simili
{loadingSimilar ? (
) : (
{similar.map((p) => { const ms = p.mediterranean_score; const msColor = ms && ms.score >= 75 ? "#16a34a" : ms && ms.score >= 50 ? "#84cc16" : ms && ms.score >= 25 ? "#f97316" : "#ef4444"; return ( ); })}
)}
)} {/* OFF JSON debug modal */} {debugProduct && ReactDOM.createPortal(
setDebugProduct(null)} >
e.stopPropagation()} >
{debugProduct.product_name}
, document.body, )}
); }