// ── Predict Basket Page ─────────────────────────────────────────────────────── function ScoreBar({ score }) { const pct = Math.round(score * 100); const color = pct >= 70 ? "#16a34a" : pct >= 40 ? "#f59e0b" : "#d1d5db"; return (
{pct}%
); } function MetaItemCard({ p, chain, checked, onToggle, colors }) { const [expanded, setExpanded] = React.useState(false); const isAll = chain === "all"; const hasVariants = isAll ? p.chain_variants && p.chain_variants.length > 0 : p.variants && p.variants.length > 0; const noVariants = !hasVariants; return (
  • {p.meta_item} {!isAll && noVariants && ( non a {chain} )}
    {p.total_qty} {p.total_qty === 1 ? "unità" : "unità"} {p.avg_interval_days && ( ogni ~{Math.round(p.avg_interval_days)}g )} {p.days_since_last}g fa {p.variant_count > 1 && {p.variant_count} prodotti}
    {/* Chain-specific variants (single chain mode) */} {!isAll && hasVariants && (
    {p.variants.length === 1 ? (
    {p.variants[0].name} {p.variants[0].brand && ( {" "} · {p.variants[0].brand} )}
    ) : ( <> {expanded && (
      {p.variants.map((v) => (
    • {v.name} {v.brand && ( · {v.brand} )}
    • ))}
    )} )}
    )} {/* All-chains variants (grouped by chain) */} {isAll && hasVariants && (
    {expanded && (
    {p.chain_variants.map((cv) => { const color = (colors && colors[cv.chain]) || { bg: "#f3f4f6", text: "#374151", }; return (
    {cv.chain}
      {cv.variants.map((v) => (
    • {v.name} {v.brand && ( {" "} · {v.brand} )}
    • ))}
    ); })}
    )}
    )}
  • ); } function BasketPage() { const [chain, setChain] = React.useState("all"); const [data, setData] = React.useState(null); const [error, setError] = React.useState(null); const [newItem, setNewItem] = React.useState(""); const [refreshKey, setRefreshKey] = React.useState(0); const [checkedItems, setCheckedItems] = React.useState({}); const colors = useChainColors(); // Load predictions React.useEffect(() => { setData(null); setError(null); fetch(`/api/predict-basket?chain=${encodeURIComponent(chain)}&limit=30`) .then((r) => (r.ok ? r.json() : Promise.reject(r.status))) .then((d) => { setData(d); setCheckedItems({}); }) .catch((e) => setError(String(e))); }, [chain, refreshKey]); async function addToList(e) { e.preventDefault(); const text = newItem.trim(); if (!text) return; await fetch("/api/shopping-list", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ item_text: text, chain: chain === "all" ? "" : chain, }), }); setNewItem(""); setRefreshKey((k) => k + 1); } async function removeFromList(id) { await fetch(`/api/shopping-list/${id}`, { method: "DELETE" }); setRefreshKey((k) => k + 1); } function toggleCheck(key) { setCheckedItems((prev) => ({ ...prev, [key]: !prev[key] })); } const checkedCount = Object.values(checkedItems).filter(Boolean).length; return (
    {/* Header */}

    Spesa

    {error && (
    Errore di caricamento: {error}
    )} {/* Shopping list — manual items */}

    Lista della spesa

    setNewItem(e.target.value)} placeholder="Aggiungi un articolo da ricordare…" className="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm bg-gray-50 focus:outline-none focus:ring-2 focus:ring-green-400 focus:bg-white" />
    {data?.shopping_list?.length > 0 ? (
      {data.shopping_list.map((s) => (
    • {s.item_text}
    • ))}
    ) : (

    Nessun articolo — aggiungi cose che non vuoi dimenticare.

    )}
    {/* Predicted meta-items */} {!data ? (
    Caricamento…
    ) : data.predictions.length === 0 ? (
    Nessuna previsione — servono almeno 3 acquisti negli ultimi 6 mesi per gli articoli abbinati.
    ) : (

    Probabilmente hai bisogno di

    {data?.chains && ( )}

    In base ai tuoi acquisti in tutte le catene. {chain !== "all" && ( <> {" "} Mostrando ciò che è disponibile a{" "} {chain}. )}

      {data.predictions.map((p) => { const key = `meta-${p.meta_item}`; return ( toggleCheck(key)} colors={colors} /> ); })}
    {checkedCount > 0 && (
    {checkedCount}{" "} {checkedCount !== 1 ? "articoli selezionati" : "articolo selezionato"}
    )}
    )}
    ); }