/* ============================================================= CoreIQ Office — Label designer + queue, Stocktake ============================================================= */ const { useState: lblUseState, useMemo: lblUseMemo, useRef: lblUseRef } = React; // ----------------------------------------------------------------- // LABEL DESIGNER + QUEUE // ----------------------------------------------------------------- function Labels() { const { pushToast } = window.useOffice(); const [tab, setTab] = lblUseState("designer"); return (
Labels

Label designer & queue

{tab === "designer" && } {tab === "queue" && } {tab === "templates" && }
); } function LabelDesigner() { const [templateId, setTemplateId] = lblUseState("lt-shelf"); const template = OD.LABEL_TEMPLATES.find(t => t.id === templateId); // Canvas elements — relative positions on the label const [elements, setElements] = lblUseState([ { id: "e1", type: "text", content: "Paracetamol 500mg", x: 4, y: 3, w: 50, h: 6, fontSize: 11, weight: 600 }, { id: "e2", type: "text", content: "100 tablets", x: 4, y: 11, w: 30, h: 4, fontSize: 8, weight: 400 }, { id: "e3", type: "text", content: "$6.95", x: 38, y: 13, w: 18, h: 9, fontSize: 14, weight: 600 }, { id: "e4", type: "barcode", content: "9300673112225", x: 4, y: 17, w: 52, h: 6, fontSize: 6, weight: 400 }, ]); const [selectedId, setSelectedId] = lblUseState("e1"); const selected = elements.find(e => e.id === selectedId); // mm → px conversion for the on-screen preview (scale 4× so small labels are visible) const SCALE = 4; function updateSel(field, value) { setElements(els => els.map(e => e.id === selectedId ? { ...e, [field]: value } : e)); } function addElement(type) { const id = "e" + Date.now(); const defaults = type === "barcode" ? { content: "9300673000000", x: 4, y: 4, w: template.width / 2, h: 5, fontSize: 6, weight: 400 } : type === "logo" ? { content: "CoreIQ", x: 4, y: 4, w: 22, h: 4, fontSize: 8, weight: 600 } : { content: "Edit me", x: 4, y: 4, w: template.width / 2, h: 4, fontSize: 9, weight: 400 }; setElements(els => [...els, { id, type: type === "logo" ? "text" : type, ...defaults }]); setSelectedId(id); } return (
{/* LEFT PANE — elements & assets */}
Add element
{[ { type: "text", label: "Text field", icon: "type" }, { type: "barcode", label: "Barcode", icon: "barcode" }, { type: "field", label: "Bind: product name", icon: "tag" }, { type: "field", label: "Bind: price", icon: "tag" }, { type: "field", label: "Bind: SKU", icon: "tag" }, { type: "field", label: "Bind: expiry", icon: "tag" }, { type: "field", label: "Bind: pack size", icon: "tag" }, { type: "logo", label: "Logo · CoreIQ", icon: "logo" }, { type: "shape", label: "Rectangle", icon: "square" }, { type: "shape", label: "Divider line", icon: "minus" }, ].map((it, i) => (
addElement(it.type)}>
{it.label}
))}
Starting template
{/* CENTRE — canvas */}
Canvas · {template.width} × {template.height} mm
Preview {SCALE}×
{ if (e.target.classList.contains('label-canvas')) setSelectedId(null); }} > {elements.map(el => (
setSelectedId(el.id)} > {el.type === "barcode" ? {el.content} : el.content}
))}
{/* RIGHT — properties */}
Properties
{!selected ? (
Select an element to edit its properties.
) : ( <>
Type {selected.type}
Content updateSel("content", e.target.value)}/>
X (mm) updateSel("x", parseFloat(e.target.value) || 0)}/>
Y (mm) updateSel("y", parseFloat(e.target.value) || 0)}/>
Width updateSel("w", parseFloat(e.target.value) || 0)}/>
Height updateSel("h", parseFloat(e.target.value) || 0)}/>
{selected.type === "text" && ( <>
Font size (pt) updateSel("fontSize", parseInt(e.target.value) || 8)}/>
Weight
)}
)}
Output
{template.printer}
); } function LabelQueue() { const { pushToast } = window.useOffice(); const queue = OD.LABEL_QUEUE; return (
{queue.filter(l => l.status === "queued").length} queued · {queue.filter(l => l.status === "printed").length} printed today
Template
Product
Store
Qty
Queued by
Status
Action
{queue.map(l => { const t = OD.LABEL_TEMPLATES.find(x => x.id === l.template); const p = findP(l.productId); const s = findS(l.store); return (
{t.name}
{p.name} · {p.strength}
{s.name}
{l.qty}
{l.queuedBy}
{l.queuedAt}
{l.status === "queued" && Queued} {l.status === "printed" && Printed {l.printedAt}} {l.status === "error" && {l.errorReason}}
{l.status === "queued" && } {l.status === "error" && } {l.status === "printed" && }
); })}
); } function LabelTemplates() { return (
{OD.LABEL_TEMPLATES.map(t => (
{t.thumb === "ndss" ? "BD Pen Needles" : t.thumb === "promo" ? "SUMMER SAVERS" : "Sample Product 500mg"}
{t.thumb === "promo" ?
30% OFF
:
9300673112225
$6.95
}
{t.name}
{t.width} × {t.height} {t.units} · {t.printer}
{t.description}
))}
); } // ================================================================= // STOCKTAKE // ================================================================= function Stocktake() { const { setScreen, setScreenState, pushToast, scope } = window.useOffice(); const [tab, setTab] = lblUseState("active"); const active = OD.STOCKTAKES.find(s => s.status === "in-progress"); return (
Stock · stocktake

Cycle counts

{tab === "active" && } {tab === "session" && } {tab === "history" && }
); } function StocktakeActive() { const { pushToast } = window.useOffice(); const inProgress = OD.STOCKTAKES.filter(s => s.status === "in-progress"); if (inProgress.length === 0) { return (
No active counts
Start a new cycle count to spot-check stock by category, supplier, or location. Variances flow into the audit log automatically.
); } return (
{inProgress.map(s => (
{s.scope}
{s.id} · {findS(s.storeId)?.name} · Started {s.started} by {s.startedBy}
{s.counted} of {s.expected} counted
·
{s.variance.count} variance{s.variance.count === 1 ? "" : "s"} ({oMoney(s.variance.value)})
))}
); } function StocktakeSession() { const { pushToast, scope } = window.useOffice(); const [filterType, setFilterType] = lblUseState("category"); const [filterValue, setFilterValue] = lblUseState("Diabetes"); const [storeId, setStoreId] = lblUseState(scope === "all" ? "S-001" : scope); // Apply filter to product list for the chosen store const items = lblUseMemo(() => { const stock = OD.STOCK[storeId] || {}; return OD.PRODUCTS .filter(p => { if (!stock[p.id]) return false; if (filterType === "category" && p.category !== filterValue) return false; if (filterType === "schedule" && p.schedule !== filterValue) return false; if (filterType === "supplier" && p.supplier !== filterValue) return false; return true; }) .map(p => ({ product: p, stock: stock[p.id] })); }, [storeId, filterType, filterValue]); // Local counts const [counts, setCounts] = lblUseState({}); const setCount = (id, v) => setCounts(c => ({ ...c, [id]: v })); const countedItems = items.filter(i => counts[i.product.id] !== undefined && counts[i.product.id] !== ""); const varianceItems = countedItems.filter(i => parseInt(counts[i.product.id]) !== i.stock.onHand); const varianceValue = varianceItems.reduce((s, i) => s + ((parseInt(counts[i.product.id]) - i.stock.onHand) * i.product.cost), 0); return (
{/* LEFT — count table */}
Scan barcode to count
Product
Expected
Counted
Lot
Expiry
Variance
{items.map((it, i) => { const cv = counts[it.product.id]; const counted = cv !== undefined && cv !== ""; const variance = counted ? parseInt(cv) - it.stock.onHand : 0; return (
{counted ? : i + 1}
{it.product.name} · {it.product.strength}
{it.product.sku} · {it.stock.location}
{it.stock.onHand}
setCount(it.product.id, e.target.value)} />
{it.stock.lot}
{it.stock.expiry}
0 ? 'var(--paid)' : variance < 0 ? 'var(--void)' : 'var(--text-muted)'}}> {counted ? (variance > 0 ? "+" : "") + variance : "—"}
); })}
{/* RIGHT — session summary */}
Cycle count session
{filterType === "category" ? filterValue : filterType === "schedule" ? filterValue : findSup(filterValue)?.name} at {findS(storeId)?.name}
{items.length} SKUs in scope
Progress
{countedItems.length}/{items.length}
Variances
{varianceItems.length}
0 ? ((countedItems.length / items.length) * 100) + "%" : "0%", background:'var(--brand)'}}>
{varianceItems.length > 0 && (
Variances detected
{varianceItems.length} item{varianceItems.length > 1 ? "s" : ""} off · net value {oMoney(varianceValue)}.
You'll be asked to attach a reason at finalisation.
)}
Actions
); } function StocktakeHistory() { return (
ID
Scope
Store
Started by
Items
Variance
Value
Status
{OD.STOCKTAKES.map(s => (
{s.id}
{s.scope}
{s.started}{s.finished ? ` → ${s.finished}` : ""}
{findS(s.storeId)?.name}
{s.startedBy}
{s.counted}/{s.expected}
{s.variance.count === 0 ? "Balanced" : s.variance.count}
{s.variance.value === 0 ? "—" : oMoney(s.variance.value)}
{s.status === "complete" && Complete} {s.status === "in-progress" && In progress} {s.status === "abandoned" && Abandoned}
))}
); } window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, { labels: Labels, stocktake: Stocktake, });