/* =============================================================
CoreIQ Office — Electronic Shelf Labels (ESL)
Manage device fleet, price sync, layout templates, batch updates
============================================================= */
const { useState: eslUseState, useMemo: eslUseMemo } = React;
// ESL sample data
const ESL_DEVICES_SUMMARY = {
total: 1842,
online: 1804,
lowBattery: 18,
needsSync: 11,
offline: 9,
};
const ESL_GATEWAYS = [
{ id: "GW-CMW-1", store: "S-001", model: "SES-imagotag · AP V2", devices: 720, signal: "−54 dBm", status: "online" },
{ id: "GW-CMW-2", store: "S-001", model: "SES-imagotag · AP V2", devices: 240, signal: "−61 dBm", status: "online" },
{ id: "GW-GLI-1", store: "S-002", model: "SES-imagotag · AP V2", devices: 410, signal: "−58 dBm", status: "online" },
{ id: "GW-RNG-1", store: "S-003", model: "SES-imagotag · AP V2", devices: 472, signal: "−52 dBm", status: "online" },
];
const ESL_TEMPLATES = [
{ id: "esl-shelf", name: "Shelf-edge · 2.9″", size: "296×128 px", uses: 1480, fields: ["name","strength","price","unitPrice","barcode"], color: false },
{ id: "esl-promo", name: "Promo highlight · 2.9″", size: "296×128 px", uses: 142, fields: ["promoBadge","name","priceWas","priceNow","ends"], color: true },
{ id: "esl-bin", name: "Dispensary bin · 4.2″", size: "400×300 px", uses: 158, fields: ["name","strength","schedule","location","barcode"], color: false },
{ id: "esl-ndss", name: "NDSS small · 1.6″", size: "200×96 px", uses: 38, fields: ["name","copay","fullPrice","eligible"], color: false },
{ id: "esl-osa", name: "On-shelf availability · 2.9″", size: "296×128 px", uses: 24, fields: ["name","stockStatus","reorderETA"], color: true },
];
const ESL_RECENT_UPDATES = [
{ ts: "Today 14:18", title: "Summer Saver · Skincare 30%", scope: "Skincare category · all stores", items: 142, status: "delivered", elapsed: "4m 12s" },
{ ts: "Today 11:48", title: "Atorvastatin price refresh", scope: "Atorvastatin 40mg · all stores", items: 3, status: "delivered", elapsed: "52s" },
{ ts: "Today 09:14", title: "PO-4422 receive · price changes", scope: "Camberwell · 4 SKUs", items: 4, status: "delivered", elapsed: "1m 04s" },
{ ts: "Yesterday", title: "Mother's Day promo · ended", scope: "Skincare · revert to retail", items: 142, status: "delivered", elapsed: "3m 48s" },
{ ts: "Yesterday", title: "NDSS sensor price update", scope: "FreeStyle Libre 2 · 3 stores", items: 3, status: "partial", elapsed: "—", note: "1 ESL offline at Ringwood" },
{ ts: "23/05/2026", title: "Vitamins · weekly refresh", scope: "All vitamins · all stores", items: 88, status: "delivered", elapsed: "5m 30s" },
];
// =================================================================
// ESL MANAGEMENT
// =================================================================
function ESLs() {
const { pushToast } = window.useOffice();
const [tab, setTab] = eslUseState("fleet");
const STORE_COLOURS = { "S-001": "var(--teal-700)", "S-002": "var(--navy-600)", "S-003": "var(--violet-600)" };
return (
Head-office function · SES-imagotag
Electronic shelf labels
{tab === "fleet" && (
<>
{/* Per-store fleet cards */}
{OD.STORES.map(s => {
const gw = ESL_GATEWAYS.filter(g => g.store === s.id);
const devices = gw.reduce((sum, g) => sum + g.devices, 0);
const lowBatt = s.id === "S-001" ? 8 : s.id === "S-002" ? 4 : 6;
const needsSync = s.id === "S-001" ? 5 : s.id === "S-002" ? 2 : 4;
const offline = s.id === "S-003" ? 4 : s.id === "S-001" ? 3 : 2;
return (
{s.code}
{s.name}
{gw.length} {gw.length === 1 ? "gateway" : "gateways"} · {devices.toLocaleString()} ESLs
{offline > 2 || lowBatt > 6
?
Attention
:
Healthy}
Online
{(devices - offline).toLocaleString()}
Needs sync
0 ? 'var(--hold-text)' : 'var(--text)'}}>{needsSync}
Low battery
6 ? 'var(--hold-text)' : 'var(--text)'}}>{lowBatt}
);
})}
{/* Gateway table */}
Gateways
Gateway
Store
Model
Devices
Signal
Last beacon
Status
{ESL_GATEWAYS.map(g => (
{g.id}
{findS(g.store)?.name}
{g.model}
{g.devices.toLocaleString()}
{g.signal}
12s ago
Online
))}
{/* Devices needing attention */}
Devices needing attention
ESL ID
Bound product
Store
Location
Battery
Last sync
Issue
{[
{ id:"E-0188-A22", store:"S-001", prod:"Insulin Glargine 100u/ml", loc:"Fridge-1", batt:18, sync:"Today 11:48", issue:"low-battery" },
{ id:"E-0188-B41", store:"S-001", prod:"Atorvastatin 40mg", loc:"Disp-B2", batt:24, sync:"Today 11:48", issue:"low-battery" },
{ id:"E-0188-C18", store:"S-003", prod:"FreeStyle Libre 2", loc:"Fridge-2", batt:62, sync:"22/05 14:20", issue:"needs-sync" },
{ id:"E-0188-D04", store:"S-003", prod:"Salbutamol Spacer", loc:"Resp-09", batt:81, sync:"Yesterday", issue:"offline" },
{ id:"E-0188-E11", store:"S-002", prod:"Esomeprazole 20mg", loc:"Disp-C1", batt:74, sync:"Today 10:14", issue:"needs-sync" },
{ id:"E-0188-F02", store:"S-001", prod:"Ventolin 100mcg inhaler", loc:"Resp-04", batt:12, sync:"Today 14:18", issue:"low-battery" },
].map(d => (
{d.id}
{d.prod}
{findS(d.store)?.code}
{d.loc}
{d.sync}
{d.issue === "low-battery" && Low battery}
{d.issue === "needs-sync" && Needs sync}
{d.issue === "offline" && Offline}
))}
>
)}
{tab === "updates" && (
Price & promotion changes are sent to ESLs in batches. Most updates land in under 5 minutes. Delivery failures retry automatically up to 3×.
When
Update
Scope
Items
Time taken
Status
{ESL_RECENT_UPDATES.map((u, i) => (
{u.ts}
{u.title}
{u.note &&
{u.note}
}
{u.scope}
{u.items}
{u.elapsed}
{u.status === "delivered" && Delivered}
{u.status === "partial" && Partial}
{u.status === "failed" && Failed}
))}
)}
{tab === "templates" && (
{ESL_TEMPLATES.map(t => (
{/* Mini preview */}
{t.id === "esl-promo" ? (
Goldfinger banana
Honduras · per lb
) : (
{t.id === "esl-ndss" ? "BD Pen Needles 4mm" : "Atorvastatin 40mg"}
{t.id === "esl-ndss" ? "100 pieces" : "30 tablets"}
9333091001284
{t.id === "esl-ndss" ? "$1.20" : "$18.20"}
)}
Used on {t.uses.toLocaleString()} {t.uses === 1 ? "ESL" : "ESLs"}
{t.fields.map(f => {f})}
))}
)}
{tab === "rules" && (
Auto-sync rules
When CoreIQ pushes price changes to ESLs automatically.
When stock receives change cost / margin
Auto-flag for review — manager confirms before push.
)}
);
}
window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, {
esls: ESLs,
});