/* =============================================================
CoreIQ Office — Shelf Marketplace (pharmacy side)
List shelf spots from the Floor Planner, run live auctions,
review & award bids, track earnings. Uses window.MARKET.
Registers window.OFFICE_SCREENS.shelfMarket (+ detail).
============================================================= */
const { useState: smUseState, useEffect: smUseEffect, useMemo: smUseMemo } = React;
// CoreIQ group store identities in the marketplace
const MY_PH = ["PH-0001", "PH-0002", "PH-0003"];
const MY_STORES = [
{ id: "PH-0001", name: "CoreIQ Camberwell", state: "VIC", tier: "Large", metro: true },
{ id: "PH-0002", name: "CoreIQ Glen Iris", state: "VIC", tier: "Medium", metro: true },
{ id: "PH-0003", name: "CoreIQ Ringwood", state: "VIC", tier: "Medium", metro: true },
];
// preset fixtures (from the Floor & Shelf Planner) the pharmacy can list
const FIXTURES = [
{ fixtureLabel: "Gondola G3", bayLabel: "Bay 2 · eye + reach", position: "Eye level", category: "Pain Management", monthlySales: 5400, facings: 4 },
{ fixtureLabel: "End-cap E1", bayLabel: "Promotional end-cap", position: "End-cap", category: "Vitamins & Supplements", monthlySales: 4300, facings: 4 },
{ fixtureLabel: "Wall Bay W2", bayLabel: "Bay 3 · eye level", position: "Eye level", category: "Skin & Suncare", monthlySales: 3700, facings: 3 },
{ fixtureLabel: "Dispensary front", bayLabel: "Counter end-cap", position: "Counter", category: "Cough & Cold", monthlySales: 3100, facings: 2 },
{ fixtureLabel: "Gondola G1", bayLabel: "Bay 1 · eye level", position: "Eye level", category: "Allergy & Hayfever", monthlySales: 4600, facings: 3 },
{ fixtureLabel: "Front counter", bayLabel: "Impulse rack", position: "Counter", category: "Confectionery", monthlySales: 2400, facings: 2 },
];
function useMktSync() { const [, s] = smUseState(0); smUseEffect(() => window.MARKET.subscribe(() => s(n => n + 1)), []); }
function useMktTick() { const [, s] = smUseState(0); smUseEffect(() => { const id = setInterval(() => s(n => n + 1), 1000); return () => clearInterval(id); }, []); }
function smCountPill(id) {
const M = window.MARKET; const st = M.effectiveStatus(M.listing(id));
if (st === "awarded") return Awarded ;
if (st === "ended") return Closed ;
return {M.fmtTimeLeft(M.timeLeft(id))} ;
}
function SmShelfStrip({ facings }) {
const n = Math.max(5, facings + 3); const hi = Math.floor(n / 2);
return (<>{Array.from({ length: n }).map((_, i) =>
= hi && i < hi + facings ? " mkt-shelf-box--hi" : "")} style={{ height: (40 + ((i * 37) % 45)) + "%" }}/>)}
>);
}
function smBidderAv(id, size = 30) {
const b = window.MARKET.BIDDERS[id] || { short: "?", color: "var(--text-subtle)" };
return
{b.short}
;
}
// =================================================================
// MAIN — tabbed
// =================================================================
function ShelfMarket() {
const M = window.MARKET;
const { setScreen, setScreenState, pushToast } = window.useOffice();
useMktSync(); useMktTick();
const [tab, setTab] = smUseState("live");
const [creating, setCreating] = smUseState(false);
const mine = M.listingsByPharmacy(MY_PH);
const live = mine.filter(l => ["open", "closing"].includes(M.effectiveStatus(l)));
const ended = mine.filter(l => M.effectiveStatus(l) === "ended");
const awarded = mine.filter(l => M.effectiveStatus(l) === "awarded");
const monthlyEarnings = awarded.reduce((s, l) => s + M.currentPrice(l.id), 0);
const liveTopBids = live.reduce((s, l) => s + M.currentPrice(l.id), 0);
function openListing(id) { setScreenState(s => ({ ...s, smListingId: id })); setScreen("shelfMarketListing"); }
const TABS = [["live", "Live auctions", live.length], ["award", "Awaiting award", ended.length], ["earnings", "Earnings", awarded.length]];
return (
Live auctions
{live.length}
{ended.length} awaiting award
Current top bids
{M.money(liveTopBids)}/mo
if awarded now
Monthly earnings
{M.money(monthlyEarnings)}/mo
{awarded.length} live placements
Annualised
{M.money(monthlyEarnings * 12)}
from shelf space
{TABS.map(([k, l, n]) => (
setTab(k)}>{l}{n > 0 ? {n} : null}
))}
{tab === "live" && (live.length ? (
{live.map(l => (
openListing(l.id)}>
{l.fixtureLabel} {l.position}
{M.money(l.monthlySales)}/mo category sales here
{M.bidCount(l.id)} bids{M.uniqueBidders(l.id)} bidders {M.leader(l.id) && leader {M.BIDDERS[M.leader(l.id)].name} }
{M.bidCount(l.id) ? "Top bid" : "Reserve"}
{M.money(M.currentPrice(l.id))}/mo
{smCountPill(l.id)}
))}
) :
)}
{tab === "award" && (ended.length ? (
Closed auctions — award the winner
Position Category Bids Top bidder Winning bid
{ended.map(l => {
const lead = M.leader(l.id);
return (
openListing(l.id)}>
{l.fixtureLabel}
{l.pharmacy} · {l.position}
{l.category}
{M.bidCount(l.id)}
{lead ? {smBidderAv(lead, 24)} {M.BIDDERS[lead].name} : No bids }
{lead ? M.money(M.currentPrice(l.id)) : "—"}
{lead ? { e.stopPropagation(); M.award(l.id, lead); pushToast({ kind: "paid", icon: "check-circle", title: "Awarded to " + M.BIDDERS[lead].name, meta: l.fixtureLabel + " · " + M.money(M.currentPrice(l.id)) + "/mo" }); }}>Award : — }
);
})}
) :
)}
{tab === "earnings" && (awarded.length ? (
Live placements & earnings
{M.money(monthlyEarnings)}/mo total
Position Category Manufacturer $/mo Term
{awarded.map(l => (
openListing(l.id)}>
{l.fixtureLabel}
{l.pharmacy} · {l.position}
{l.category}
{smBidderAv(l.awardedTo, 24)} {M.BIDDERS[l.awardedTo] ? M.BIDDERS[l.awardedTo].name : "—"}
{M.money(M.currentPrice(l.id))}
{l.term}mo
))}
) :
)}
{creating &&
setCreating(false)} onCreate={(o) => { const l = M.createListing(o); setCreating(false); pushToast({ kind: "paid", icon: "check-circle", title: "Listing live", meta: l.fixtureLabel + " · reserve " + M.money(l.reserve) + "/mo" }); setTab("live"); }}/>}
);
}
function EmptyCard({ icon, title, meta }) {
return
;
}
// =================================================================
// CREATE LISTING (modal) — pick a fixture from the planner
// =================================================================
function CreateListing({ onClose, onCreate }) {
const M = window.MARKET;
const [storeId, setStoreId] = smUseState(MY_STORES[0].id);
const [fxIdx, setFxIdx] = smUseState(0);
const [rate, setRate] = smUseState(10); // % of sales as reserve
const [term, setTerm] = smUseState(6);
const [days, setDays] = smUseState(3);
const [catOnly, setCatOnly] = smUseState(true);
const fx = FIXTURES[fxIdx];
const store = MY_STORES.find(s => s.id === storeId);
const reserve = Math.round(fx.monthlySales * rate / 100 / 5) * 5;
function submit() {
onCreate({
pharmacyId: store.id, pharmacy: store.name, state: store.state, tier: store.tier, metro: store.metro,
category: fx.category, fixtureLabel: fx.fixtureLabel, bayLabel: fx.bayLabel, position: fx.position,
monthlySales: fx.monthlySales, facings: fx.facings, reserve, term, days,
eligibility: { categoryOnly: catOnly },
});
}
return (
e.stopPropagation()}>
List a shelf spot Pick a fixture from your Floor & Shelf Planner. The reserve is anchored on the category sales the position drives.
Store
setStoreId(e.target.value)}>
{MY_STORES.map(s => {s.name} · {s.state} )}
Fixture & position (from planner)
{FIXTURES.map((f, i) => (
setFxIdx(i)} style={{ cursor: "pointer", border: "1px solid " + (i === fxIdx ? "var(--brand)" : "var(--border)"), borderRadius: 10, padding: "11px 13px", background: i === fxIdx ? "var(--brand-bg)" : "var(--surface)", boxShadow: i === fxIdx ? "0 0 0 3px var(--brand-bg)" : "none" }}>
{f.fixtureLabel}
{f.category}
{f.bayLabel} · {f.facings} facings
{M.money(f.monthlySales)}/mo sales
))}
Term
setTerm(+e.target.value)}>
{[3, 6, 12].map(t => {t} months )}
Auction runs
setDays(+e.target.value)}>
{[1, 3, 5, 7].map(d => {d} days )}
setCatOnly(e.target.checked)}/>
Restrict bidding to {fx.category} manufacturers
Cancel
Publish auction
);
}
// =================================================================
// DETAIL — review bids & award
// =================================================================
function ShelfMarketListing() {
const M = window.MARKET;
const { screenState, setScreen, pushToast } = window.useOffice();
useMktSync(); useMktTick();
const id = screenState.smListingId;
const l = M.listing(id);
if (!l) return
;
const st = M.effectiveStatus(l);
const bids = M.bidsFor(id);
const lead = M.leader(id);
const maxSales = Math.max(...l.salesHistory, 1);
const months = ["6mo", "5mo", "4mo", "3mo", "2mo", "Now"];
function awardTo(bidder) { M.award(id, bidder); pushToast({ kind: "paid", icon: "check-circle", title: "Awarded to " + M.BIDDERS[bidder].name, meta: l.fixtureLabel + " · " + M.money(bids.find(b => b.bidder === bidder).amount) + "/mo" }); }
return (
Position value
Reserve is anchored on the category sales this spot drives
{M.money(l.monthlySales)}/mo category sales from this position
Reserve
{M.money(l.reserve)}/mo
{Math.round(l.reserve / l.monthlySales * 100)}% of sales
Category sales · last 6 months
{l.salesHistory.map((v, i) =>
)}
Fixture {l.fixtureLabel}
Bay {l.bayLabel}
Position {l.position}
Facings {l.facings}
Term {l.term} months
Eligibility {l.eligibility.categoryOnly ? l.category + " only" : "Open"}
{/* side — bids + award */}
{st === "awarded" ? "Awarded" : st === "ended" ? "Closed · review bids" : "Closes in"}
{st === "awarded" ? "Done" : st === "ended" ? M.money(M.currentPrice(id)) : M.fmtTimeLeft(M.timeLeft(id))}
{M.bidCount(id) ? "Top bid" : "Reserve"}
{M.money(M.currentPrice(id))}/mo
Total contract
{M.money(M.currentPrice(id) * l.term)}
{st === "awarded" ? (
Awarded to {M.BIDDERS[l.awardedTo] ? M.BIDDERS[l.awardedTo].name : "—"}
) : lead ? (
<>
awardTo(lead)}> Award to {M.BIDDERS[lead].name}
{st === "ended" ? "Auction closed — award the highest bid, or pick another bidder below." : "You can award early to the current leader, or wait for close."}
>
) :
No bids yet.
}
Bids
{bids.length} · {M.uniqueBidders(id)} bidders
{bids.length === 0 ?
No bids yet.
: (
{bids.map((b, i) => (
{smBidderAv(b.bidder)}
{M.BIDDERS[b.bidder].name}{i === 0 && High }
{M.money(b.amount)}/mo
{st !== "awarded" &&
awardTo(b.bidder)}>Award }
))}
)}
);
}
window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, { shelfMarket: ShelfMarket, shelfMarketListing: ShelfMarketListing });