// Dashboard, Inventory, Product detail screens const H = window.DATA_HELPERS; const TODAY_STR = "2026-04-25"; // ─── Helpers shared across screens ───────────────────────────────── const remainingDisplay = (p) => { const cfg = window.SAMPLE_DATA.types.find(t => t.id === p.type); if (p.kind === "discrete") { const cur = p.countLastAudit != null ? p.countLastAudit : p.countOriginal; return `${cur} / ${p.countOriginal} ${cfg?.unit || "ct"}`; } const est = H.estimatedRemaining(p, TODAY_STR); return `${est.toFixed(2).replace(/\.?0+$/,"")} / ${p.weight} ${cfg?.unit || "g"}`; }; const remainingShort = (p) => { if (p.kind === "discrete") { const cur = p.countLastAudit != null ? p.countLastAudit : p.countOriginal; return `${cur} ct`; } const cfg = window.SAMPLE_DATA.types.find(t => t.id === p.type); const est = H.estimatedRemaining(p, TODAY_STR); return `${est.toFixed(2).replace(/\.?0+$/,"") || "0"} ${cfg?.unit || "g"}`; }; const Dashboard = ({data, stats, onNav, onSelectProduct, onAudit, onMarkGone}) => { const series30 = stats.series30.map(d => ({ date: d.date, value: d.grams, label: "" })); // Type breakdown const typeColors = { "Flower": "var(--sage)", "Concentrate": "var(--terracotta)", "Edible": "var(--amber)", "Vaporizer": "var(--plum)", "Pre-roll": "oklch(50% 0.06 200)", "Tincture": "oklch(55% 0.06 270)" }; const segments = Object.entries(stats.typeBreakdown).map(([k, v]) => ({ label: k, value: v, color: typeColors[k] || "var(--ink-3)" })); // Sparklines const last7Series = stats.series7.map(l => l.grams); const last30Series = series30.map(d => d.value); const overdue = stats.overdueAudits; const lowBulk = stats.lowStockBulk; const lowDiscrete = stats.lowStockDiscreteGroups; return (
{/* Header */}
Saturday · April 25, 2026

Good evening.

onNav("add")}>New product onNav("audit")}>Audit onNav("consume")}>Mark finished
{stats.activeCount} active items across {data.bins.length} bins · {stats.consumedCount} consumed · {stats.goneCount} gone. {overdue.length > 0 && · {overdue.length} audit{overdue.length === 1 ? "" : "s"} overdue.}
{/* Top stats row */}
} /> 0 ? ` · ${fmt.money(stats.goneSpend)} lost` : ""}`} /> } />
{/* Audit alert strip */} {overdue.length > 0 && (
Audit overdue
{overdue.length} item{overdue.length === 1 ? "" : "s"} haven't been checked in a while
{overdue.slice(0, 3).map(p => p.name).join(" · ")} {overdue.length > 3 && ` · +${overdue.length - 3} more`}
onAudit && onAudit(overdue[0])}>Run audit
)} {/* Main grid */}
{/* Consumption chart */}
Consumption
Last 30 days
{fmt.g(stats.series30.reduce((s,l)=>s+l.grams,0))} est. total
{stats.avgGap.toFixed(0)} day avg between buys
({...d, label: ""}))} height={140} color="var(--sage)" />
30 days ago 15 days ago today
{/* Type breakdown */}
By type · grams on hand
Inventory
{segments.map(s => (
{s.label}
{s.value.toFixed(1)}g
))}
{/* Bottom row: shop + brand + low stock */}
Favorite shop
{stats.favShop[0]}
{stats.favShop[1]} of {data.products.length} purchases
Favorite brand
{stats.favBrand[0]}
{stats.favBrand[1]} purchases
Low stock · running out
{lowBulk.length + lowDiscrete.length} item{(lowBulk.length + lowDiscrete.length) === 1 ? "" : "s"}
{(lowBulk.length + lowDiscrete.length) === 0 &&
Nothing running low.
} {lowBulk.slice(0, 3).map(p => { const pct = H.pctRemaining(p, TODAY_STR); return (
onSelectProduct(p)} style={{padding: "10px 20px", borderTop: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, cursor: "pointer"}}>
{p.name}
{H.brandName(data, p.brandId)} · {p.type}
{remainingShort(p)}
); })} {lowDiscrete.slice(0, 2).map(g => (
onSelectProduct(g.items[0])} style={{padding: "10px 20px", borderTop: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, cursor: "pointer"}}>
{g.name}
{H.brandName(data, g.brandId)} · {g.type}
{g.totalCount} left
))}
); }; // ─── INVENTORY ───────────────────────────────────────────────────── const Inventory = ({data, onSelectProduct, onNav}) => { const [filter, setFilter] = React.useState("active"); // active | consumed | gone | all const [typeFilter, setTypeFilter] = React.useState("all"); const [sortBy, setSortBy] = React.useState("recent"); const [search, setSearch] = React.useState(""); let products = data.products; if (filter === "active") products = products.filter(p => p.status === "active"); else if (filter === "consumed") products = products.filter(p => p.status === "consumed"); else if (filter === "gone") products = products.filter(p => p.status === "gone"); if (typeFilter !== "all") products = products.filter(p => p.type === typeFilter); if (search) { const q = search.toLowerCase(); products = products.filter(p => { const brand = H.brandName(data, p.brandId).toLowerCase(); const shop = H.shopName(data, p.shopId).toLowerCase(); return p.name.toLowerCase().includes(q) || brand.includes(q) || shop.includes(q) || p.sku.toLowerCase().includes(q); }); } products = [...products].sort((a, b) => { if (sortBy === "recent") return new Date(b.purchaseDate) - new Date(a.purchaseDate); if (sortBy === "name") return a.name.localeCompare(b.name); if (sortBy === "thc") return b.thc - a.thc; if (sortBy === "remaining") return H.estimatedRemaining(b, TODAY_STR) - H.estimatedRemaining(a, TODAY_STR); if (sortBy === "price") return b.price - a.price; if (sortBy === "audit") return H.daysSinceCheck(b, TODAY_STR) - H.daysSinceCheck(a, TODAY_STR); return 0; }); return (
{products.length} items

Inventory

onNav("audit")}>Audit onNav("add")}>New product
{/* Toolbar */}
{/* Tabs */}
{[["active", "Active"], ["consumed", "Consumed"], ["gone", "Gone"], ["all", "All"]].map(([k, l]) => ( ))}
{/* Search */}
setSearch(e.target.value)} style={{border: "none", outline: "none", background: "transparent", padding: "8px 0", fontSize: 13, flex: 1, color: "var(--ink)"}} />
{/* Table */}
Product
Brand
Shop
THC %
Price
Remaining
Last checked
Bin
{products.length === 0 && (
No items match these filters.
)} {products.map(p => { const bin = data.bins.find(b => b.id === p.binId); const pctRemaining = H.pctRemaining(p, TODAY_STR); const overdue = H.auditOverdue(data, p, TODAY_STR); const sinceCheck = H.daysSinceCheck(p, TODAY_STR); const last = H.lastAudit(p); const isInactive = p.status !== "active"; return (
onSelectProduct(p)} className="inv-row" style={{ display: "grid", gridTemplateColumns: "32px 2fr 1fr 1fr 0.6fr 0.6fr 0.9fr 0.9fr 0.8fr", padding: "14px 20px", borderBottom: "1px solid var(--line)", alignItems: "center", cursor: "pointer", opacity: isInactive ? 0.55 : 1, fontSize: 13 }}>
{TYPE_GLYPHS[p.type]}
{p.name} {p.status === "consumed" && Consumed} {p.status === "gone" && Gone} {p.status === "active" && overdue && Audit due}
{p.sku}{p.assetTag ? ` · ${p.assetTag}` : ""}
{H.brandName(data, p.brandId)}
{H.shopName(data, p.shopId)}
{p.thc.toFixed(1)}
{fmt.money(p.price)}
{remainingShort(p)}
{p.status === "active" && p.kind === "bulk" && (
)}
{p.status !== "active" ? archived : last ? {sinceCheck}d ago · {last.mode} : never}
{bin ? bin.name : }
); })}
); }; // ─── PRODUCT DETAIL ──────────────────────────────────────────────── const ProductDetail = ({product, data, onClose, onConsume, onMarkGone, onAudit, onEdit}) => { const bin = data.bins.find(b => b.id === product.binId); const cfg = data.types.find(t => t.id === product.type); const pctRemaining = H.pctRemaining(product, TODAY_STR); const est = H.estimatedRemaining(product, TODAY_STR); const last = H.lastAudit(product); const overdue = H.auditOverdue(data, product, TODAY_STR); const sinceCheck = H.daysSinceCheck(product, TODAY_STR); const isActive = product.status === "active"; return (
e.stopPropagation()} style={{ width: "min(720px, 100vw)", height: "100%", background: "var(--bg)", borderLeft: "1px solid var(--line)", overflow: "auto", boxShadow: "var(--shadow-lg)" }}> {/* Header */}
Product · {product.sku}
{isActive && onAudit(product)}>Audit} {isActive && onConsume(product)}>Mark finished} {isActive && onMarkGone(product)}>Mark gone}
{/* Identity */}
{TYPE_GLYPHS[product.type]} {product.type}
{product.status === "consumed" && Consumed · {fmt.daysAgo(product.consumedDate)}} {product.status === "gone" && Gone · {fmt.daysAgo(product.goneDate)}} {isActive && overdue && Audit overdue · {sinceCheck}d}

{product.name}

{H.brandName(data, product.brandId)} · from {H.shopName(data, product.shopId)}
{/* Hero stats */}
{[ ["Price", fmt.money(product.price)], [product.kind === "discrete" ? "Quantity" : "Size", product.kind === "discrete" ? `${product.countOriginal} ${cfg?.unit || "ct"}` : `${product.weight} ${cfg?.unit || "g"}` ], ["THC", `${product.thc.toFixed(1)}%`], ["CBD", `${product.cbd.toFixed(1)}%`] ].map(([l, v]) => (
{l}
{v}
))}
{/* Remaining + audit */} {isActive && (
{product.kind === "discrete" ? "Units remaining" : "Estimated remaining"}
{product.kind === "discrete" ? `${product.countLastAudit != null ? product.countLastAudit : product.countOriginal} of ${product.countOriginal}` : `${est.toFixed(2)} of ${product.weight} ${cfg?.unit || "g"}`} {Math.round(pctRemaining*100)}%
{product.kind === "bulk" && last && (
Estimated by linear decay since last {last.mode} on {fmt.dateShort(last.date)} ({last.value}{cfg?.unit}). Re-audit to update.
)}
)} {/* Audit history */}
Audit history
{isActive && }
{(!product.audits || product.audits.length === 0) ? (
No audits recorded. Cadence for {product.type}: every {cfg?.cadenceDays || "—"} days.
) : (
{[...product.audits].reverse().map((a, i) => (
{a.mode === "weigh" && "Weighed"} {a.mode === "estimate" && "Estimated"} {a.mode === "presence" && (a.confirmedBy === "lost" ? "Marked lost" : "Confirmed presence")}
{fmt.date(a.date)} · {fmt.daysAgo(a.date)}
{a.value} {cfg?.unit}
was {a.prev} {cfg?.unit}
))}
)}
{/* Details list */}
Details
{[ ["SKU", {product.sku}], ["Asset tag", product.assetTag ? {product.assetTag} : None], ["Type", `${product.type} · ${product.kind}`], ["Brand", H.brandName(data, product.brandId)], ["Shop", H.shopName(data, product.shopId)], ["Total cannabinoids", `${product.totalCannabinoids.toFixed(1)}%`], ["Purchase date", fmt.date(product.purchaseDate)], ["Bin", bin ? `${bin.name} — ${bin.location}` : ], ["Audit cadence", `Every ${cfg?.cadenceDays || "—"} days · ${cfg?.auditMode || "—"}`], ["Cost per gram", product.kind === "bulk" && product.weight > 0 ? fmt.money(product.price / product.weight) : product.kind === "discrete" && product.unitWeight > 0 ? `${fmt.money(product.price / (product.countOriginal * product.unitWeight))} (effective)` : "—" ], ...(product.status === "consumed" ? [ ["Date finished", fmt.date(product.consumedDate)], ["Lasted", `${Math.round((new Date(product.consumedDate) - new Date(product.purchaseDate))/86400000)} days`] ] : []), ...(product.status === "gone" ? [ ["Date gone", fmt.date(product.goneDate)], ["After", `${Math.round((new Date(product.goneDate) - new Date(product.purchaseDate))/86400000)} days`] ] : []) ].map(([l, v], i) => (
{l} {v}
))}
{/* Final notes */} {(product.status === "consumed" || product.status === "gone") && (
{product.status === "gone" ? "Why it's gone" : "Final notes"}
{product.status === "consumed" && (
{[1,2,3,4,5].map(n => ( ))}
)}
"{product.notes || 'No notes recorded.'}"
)}
); }; Object.assign(window, { Dashboard, Inventory, ProductDetail, remainingDisplay, remainingShort });