/* Source-derived physical ticket artwork; rows are resolved by the scoped API. */ (function () { const {useState,useEffect}=React; const MM=96/25.4,PREVIEW=.5,MARGIN=6,GUTTER=4; const FORMATS={shelf:{id:"shelf",label:"Shelf ticket",w:70,h:50,description:"Slots into the standard rail. One per facing."},barker:{id:"barker",label:"Bay barker",w:95,h:70,description:"Projects from the shelf edge for offers seen from the aisle."},a5:{id:"a5",label:"A5 poster",w:148,h:210,description:"One-up on A4, with a trim edge for an end-of-bay or window."}}; const THEMES={red:{label:"Classic red",flag:"#d81f2a",fg:"#fff",ink:"#b3141d",tint:"#fdecec"},amber:{label:"Clearance",flag:"#f4a300",fg:"#241a00",ink:"#a06a00",tint:"#fff6e3"},teal:{label:"Brand teal",flag:"#0d7b6c",fg:"#fff",ink:"#0a6156",tint:"#e6f4f1"},magenta:{label:"Beauty pink",flag:"#c2185b",fg:"#fff",ink:"#a01449",tint:"#fdeaf1"},green:{label:"Wellness",flag:"#2e7d32",fg:"#fff",ink:"#256428",tint:"#eaf4ea"},violet:{label:"Premium",flag:"#5b3fa8",fg:"#fff",ink:"#4a3389",tint:"#efeafa"},navy:{label:"Navy",flag:"#0f2436",fg:"#fff",ink:"#0f2436",tint:"#e9eef3"}}; const money=c=>c==null?"—":"$"+(c/100).toFixed(2); const date=d=>d?String(d).slice(0,10).split('-').reverse().join('/'):"—"; const layout=f=>{const cols=Math.floor((210-2*MARGIN+GUTTER)/(f.w+GUTTER)),rows=Math.floor((297-2*MARGIN+GUTTER)/(f.h+GUTTER));return {cols,rows,per:cols*rows,gridW:cols*f.w+(cols-1)*GUTTER,gridH:rows*f.h+(rows-1)*GUTTER};}; function Ticket({row,format,promotion,theme}) { const f=FORMATS[format],t=THEMES[theme||row.theme]||THEMES.red,scale=format==='shelf'?1:format==='barker'?1.42:2.1; const price=row.kind==='price'&&row.effectiveCents!=null; const member=promotion?.mechanic==='member',clearance=promotion?.origin==='clearance'; const parts=price?(row.effectiveCents/100).toFixed(2).split('.'):[]; const saving=price&&row.beforeCents!=null?row.beforeCents-row.effectiveCents:0; return
{row.draft||row.preview?"DRAFT PREVIEW":clearance?"CLEARANCE":member?"MEMBER PRICE":price?"SPECIAL":"OFFER"}{saving>0&&SAVE {money(saving)}}
{row.name}
{price?
${parts[0]}{parts[1]}
:
{row.offerText||"See offer conditions"}
}
{row.beforeCents!=null?(price?"Shelf price ":"Each ")+money(row.beforeCents):""}{date(row.startsOn)} – {date(row.endsOn)}
{member?"Loyalty card must be presented. ":""}{clearance?"Short-dated stock. ":""}While stocks last.{row.sku}
; } function PromoTickets() { const {screenState={},setScreen,activeStoreId}=window.useOffice(); const [format,setFormat]=useState('shelf'),[pick,setPick]=useState(screenState.promotionTicketId||'all'),[siteId,setSiteId]=useState(activeStoreId||''),[asOf,setAsOf]=useState(''); const [data,setData]=useState(null),[busy,setBusy]=useState(true),[error,setError]=useState(null),[colours,setColours]=useState({}),[printRoot,setPrintRoot]=useState(null); useEffect(()=>{const root=document.createElement('div');root.className='promo-ticket-print-root';document.body.appendChild(root);document.body.classList.add('office-promo-tickets-active');setPrintRoot(root);return()=>{document.body.classList.remove('office-promo-tickets-active');root.remove();};},[]); useEffect(()=>{setSiteId(activeStoreId||'');},[activeStoreId]); useEffect(()=>{let alive=true;setBusy(true);setError(null);setData(null); window.OfficeAPI.promotionsWorkspace({...siteId?{siteId}:{},...asOf?{asOf}:{}}).then(async result=>{if(result.promotions?.some(p=>p.id===pick&&p.state==='draft')){result=await window.OfficeAPI.promotionsWorkspace({siteId:siteId||result.siteId||undefined,asOf:asOf||undefined,draftId:pick});}if(alive)setData(result);}).catch(e=>{if(alive)setError(e.message||"Couldn't load printable tickets.");}).finally(()=>{if(alive)setBusy(false);}); return()=>{alive=false;}; },[siteId,asOf,pick]); const all=data?.tickets?.rows||[],promotions=data?.promotions||[],f=FORMATS[format],L=layout(f); const rows=all.filter(row=>pick==='all'||row.promotionId===pick); const held=(data?.tickets?.held||[]).filter(row=>pick==='all'||row.promotionId===pick); const options=promotions.filter(p=>p.channels?.includes('ticket')||all.some(row=>row.promotionId===p.id)); const draftPreview=!!data?.tickets?.draftPreview||rows.some(row=>row.draft||row.preview); const sheets=Math.ceil(rows.length/L.per),ready=!busy&&!!data?.siteId&&rows.length>0; const promotionFor=row=>promotions.find(p=>p.id===row.promotionId); const tickets=items=>items.map(row=>); const print=()=>{if(!ready)return;try{window.print();}catch(e){setError(e.message||"Couldn't open the print dialog.");}}; return

Shelf tickets

Resolved from published promotion prices and register rules for the chosen date. Basket offers remain offers, not invented unit prices.
{busy&&
Loading resolved tickets…
}{error&&
{error}
} {data&&
{rows.length} tickets · {sheets} A4 sheets · {L.cols} × {L.rows} per page · {data.siteName||'Choose a store'}Shown at half size
{draftPreview&&
DRAFT PREVIEW — not published. These tickets show the draft calculation and must not be displayed as a live offer.
}{(data.tickets?.warnings||[]).map((warning,i)=>
{warning}
)} {!data.siteId&&
Select a store before printing tickets.
} {Array.from({length:sheets},(_,i)=>
Sheet {i+1} of {sheets} · A4
{tickets(rows.slice(i*L.per,(i+1)*L.per))}
)} {!rows.length&&
No printable tickets for this promotion, store and effective date.
}
}
{printRoot&&window.ReactDOM.createPortal(ready?Array.from({length:sheets},(_,i)=>
{tickets(rows.slice(i*L.per,(i+1)*L.per))}
):null,printRoot)}
; } window.OFFICE_SCREENS=Object.assign(window.OFFICE_SCREENS||{},{promoTickets:PromoTickets}); })();