// VMAX POS — the order screen: menu grid, ticket, variant (hot/cold + size) sheet, // member attach + recall view + new-member enrol, and the low-stock nudge. (function () { const DS = window.VMAX365DesignSystem_0f78b2; const { MenuItemCard, CategoryChip, TempSelector, SizeSelector, TempTag, SegmentedControl, QtyStepper, Button, Badge } = DS; const { Icon, Cup, Sheet, Overline } = window.VMAXUI; const { Avatar, EmptyState, TextInput, Field, pushToast } = window.VMAXW; const { K, categories, sizes } = window.VMAXP; const { useVMAX, lowStock } = window.VMAXStore; const { PaymentSheet, OrdersSheet } = window.VMAXPAY; const { RequestStockSheet } = window.VMAXEXTRA; const sizeLabel = (s) => ({ reg: 'Regular', lg: 'Large', jumbo: 'Jumbo' }[s] || ''); const hasVariants = (m) => m.hotcold || m.sizes; // ---------- Variant sheet ---------- function VariantSheet({ item, onClose, onAdd }) { const [temp, setTemp] = React.useState('cold'); const [size, setSize] = React.useState('reg'); React.useEffect(() => { if (item) { setTemp(item.defaultTemp || 'cold'); setSize('reg'); } }, [item]); if (!item) return null; const up = (sizes.find((s) => s.value === size) || {}).upcharge || 0; const price = item.price + up, member = item.member + up; return (
{item.name}
{item.hotcold && Served} {item.sizes && Size}
{K(price)}
); } // ---------- Member: search → recall view → attach; or enrol new ---------- function MemberSheet({ open, onClose }) { const [s, a] = useVMAX(); const [q, setQ] = React.useState(''); const [view, setView] = React.useState(null); // a member object (recall view) const [enrol, setEnrol] = React.useState(false); const [form, setForm] = React.useState({ name: '', phone: '' }); React.useEffect(() => { if (open) { setQ(''); setView(null); setEnrol(false); setForm({ name: '', phone: '' }); } }, [open]); const list = s.members.filter((m) => (m.name + m.phone).toLowerCase().includes(q.toLowerCase())); if (view) { const fav = [...view.history].sort((x, y) => y.times - x.times); return (
{view.name}
{view.phone} · member since {view.since}
Member
Recognition only — ring the order at the counter as usual.
{fav.length ? 'Their usuals' : 'No history yet'} {fav.length ? (
{fav.map((h, i) => (
{i === 0 && }
{h.name}{h.temp && {h.temp}}
{h.zh &&
{h.zh}
}
×{h.times}
))}
) :

Just joined — nothing here yet. Their next orders will start building their usuals.

}
); } if (enrol) { return (
New member

The customer scans this and enters their own details on their phone. For the demo, enter them here:

setForm({ ...form, name: v })} placeholder="Full name" autoFocus /> setForm({ ...form, phone: v })} placeholder="097 000 0000" />
); } return (
Member
setQ(e.target.value)} placeholder="Search name or phone…" style={{ border: 'none', background: 'transparent', padding: '13px 0', fontFamily: 'var(--font-body)', fontSize: 15, width: '100%', outline: 'none', color: 'var(--vmax-ink)' }} />
{list.map((m) => ( ))} {list.length === 0 &&
No member matches that.
}
); } // ---------- Order screen ---------- function OrderSurface({ session }) { const [s, a] = useVMAX(); const [cat, setCat] = React.useState('All'); const [search, setSearch] = React.useState(''); const [sheetItem, setSheetItem] = React.useState(null); const [memberOpen, setMemberOpen] = React.useState(false); const [payOpen, setPayOpen] = React.useState(false); const [ordersOpen, setOrdersOpen] = React.useState(false); const [nudge, setNudge] = React.useState(true); const [reqOpen, setReqOpen] = React.useState(false); const order = s.order; const member = order.member; const menu = s.menu; let list = menu.filter((m) => cat === 'All' || m.cat === cat); if (search) list = menu.filter((m) => m.name.toLowerCase().includes(search.toLowerCase())); const series = [...new Set(list.map((m) => m.series))]; const sub = order.lines.reduce((acc, o) => acc + (member ? o.member : o.price) * o.qty, 0); const ordSub = order.lines.reduce((acc, o) => acc + o.price * o.qty, 0); const discount = member ? ordSub - order.lines.reduce((acc, o) => acc + o.member * o.qty, 0) : 0; const low = lowStock(s); const lowItem = low[0]; const tap = (m) => { if (hasVariants(m)) setSheetItem(m); else a.addLine({ ...m, temp: null, size: null, price: m.price, member: m.member }); }; return (
{/* MENU */}
setSearch(e.target.value)} placeholder="Search drinks…" style={{ border: 'none', background: 'transparent', padding: '12px 0', fontFamily: 'var(--font-body)', fontSize: 15, width: '100%', outline: 'none', color: 'var(--vmax-ink)' }} />
{menu.length > 0 &&
{categories.map((c) => { setCat(c); setSearch(''); }}>{c})}
}
{menu.length === 0 && } {menu.length > 0 && series.length === 0 &&
No drinks match that. Check the spelling or pick a category.
} {series.map((ser) => (
{ser}
{list.filter((m) => m.series === ser).map((m) => tap(m)} />)}
))}
{nudge && lowItem && (
{lowItem.front === 0 ? 'Out of' : 'Running low on'} {lowItem.name.toLowerCase()}
)}
{/* TICKET */}
Order #A-0{s.orderNo}
{order.lines.length === 0 ? (

Tap a drink to start the order. Hot or cold is chosen as you add it.

) : (
{order.lines.map((o, i) => (
{o.name}
{o.temp && }{o.size && {sizeLabel(o.size)}}
a.bumpLine(i, v)} min={0} />
{K((member ? o.member : o.price) * o.qty)}
))}
)} {order.lines.length > 0 && (
{member && }
Total {K(sub)}
)}
setSheetItem(null)} onAdd={(l) => { a.addLine(l); setSheetItem(null); }} /> setMemberOpen(false)} /> setPayOpen(false)} /> setOrdersOpen(false)} session={session} /> setReqOpen(false)} who={session.active ? session.active.name : ''} preset={lowItem ? lowItem.id : null} />
); } function Row({ k, v, accent }) { return
{k}{v}
; } window.VMAXORDER = { OrderSurface }; })();