/* =============================================================
CoreIQ Office — Supplier profile (detail page with tabs)
Registers window.OFFICE_SCREENS.supplierDetail
============================================================= */
const { useState: sdUseState, useMemo: sdUseMemo, useEffect: sdUseEffect } = React;
const MONTHS = ["Jun","Jul","Aug","Sep","Oct","Nov","Dec","Jan","Feb","Mar","Apr","May"];
function poStatusBadge(st) {
const m = {
received: { c: "badge--paid", l: "Received" },
"in-transit":{ c: "badge--hold", l: "In transit" },
ordered: { c: "badge--brand", l: "Ordered" },
part: { c: "badge--hold", l: "Part-filled" },
}[st] || { c: "badge--neutral", l: st };
return {m.l} ;
}
function invStatusBadge(st) {
const m = {
paid: { c: "badge--paid", l: "Paid" },
due: { c: "badge--hold", l: "Due" },
overdue: { c: "badge--void", l: "Overdue" },
credit: { c: "badge--brand", l: "Credit" },
}[st] || { c: "badge--neutral", l: st };
return {m.l} ;
}
function ago(d) { return d === 0 ? "Today" : d === 1 ? "Yesterday" : d + " days ago"; }
// ---- Overview tab ------------------------------------------------
function TabOverview({ id, period, setTab }) {
const ext = window.supExt(id);
const sp = window.supSpend(id, period);
const share = window.supSpendShare(id, period);
const delta = window.supSpendDelta(id);
const trend = window.supSpend12(id);
const tmax = Math.max(...trend, 1);
const bo = window.supBackorders(id);
const openBal = window.supOpenBalance(id);
const t = window.supType(id);
const KpiCard = window.KpiCard;
return (
{bo.length > 0 && (
= 6 ? " sup-alert--bad" : "")}>
{bo.length} line{bo.length > 1 ? "s" : ""} on backorder with this supplier — {bo.reduce((s, b) => s + b.qty, 0)} units across affected stores.
setTab("backorders")}>View
)}
p.key === period).label} value={window.supMoneyK(sp)} delta={isFinite(delta) && sp > 0 ? { dir: delta >= 0 ? "up" : "down", text: (delta >= 0 ? "+" : "") + (delta * 100).toFixed(1) + "% MoM" } : { text: "New account" }}/>
= 95 ? "up" : "down", text: ext.leadTime + "-day lead" }}/>
0 ? window.supMoneyK(openBal) : "$0"} delta={{ text: ext.terms.payment }}/>
Spend · trailing 12 months Total {window.supMoneyK(trend.reduce((a, b) => a + b, 0))}
{trend.map((v, i) => (
= 6 ? t.color : undefined }} title={window.supMoneyK(v)}/>
{MONTHS[i]}
))}
Account rep
{ext.rep.name.split(" ").map(w => w[0]).slice(0, 2).join("")}
{ext.rep.name}
{ext.rep.title}
{ext.rep.mobile}
{ext.channel}
);
}
// ---- Catalogue tab ----------------------------------------------
function TabCatalogue({ id }) {
const cat = window.supCatalogue(id);
return (
SKUs supplied
{cat.length}
Link SKU
Product PDE Buy ex Sell MOQ On hand
{cat.map(p => (
{p.name} {p.strength !== "—" ? p.strength : ""}
{p.pack}{p.schedule !== "—" ? " · " + p.schedule : ""}{p.pbs ? " · PBS" : ""}
{p.pde}
{window.officeMoney(p.cost)}
{window.officeMoney(p.price)}
{p.moq}
{p.onHand}
))}
{cat.length === 0 &&
No SKUs linked to this supplier.
}
);
}
// ---- Purchase orders tab ----------------------------------------
function TabPOs({ id }) {
const pos = window.supPurchaseOrders(id);
const { pushToast } = window.useOffice();
return (
Purchase orders
{pos.length}
pushToast({ kind: "paid", icon: "plus", title: "New purchase order", meta: "Draft started for this supplier" })}> New PO
PO number Store Lines Date Status Value
{pos.map(po => (
{po.id}
{po.store}
{po.lines}
{ago(po.daysAgo)}{po.eta ? " · ETA " + po.eta : ""}
{poStatusBadge(po.status)}
{window.officeMoney(po.value)}
))}
);
}
// ---- Invoices tab -----------------------------------------------
function TabInvoices({ id }) {
const inv = window.supInvoices(id);
const open = window.supOpenBalance(id);
return (
<>
Open balance
{window.supMoneyK(open)}
{window.supExt(id).terms.payment}
Settlement
{window.supExt(id).terms.settlement || "—"}
early-payment discount
Credits · 12mo
{window.supExt(id).credits}%
of purchases returned
Invoices & credits {inv.length} Log credit
Document Date Terms Status Amount
{inv.map(v => (
{v.id}
{ago(v.daysAgo)}
{v.terms}
{invStatusBadge(v.status)}
{window.officeMoney(v.amount)}
))}
>
);
}
// ---- Performance tab --------------------------------------------
function TabPerformance({ id }) {
const ext = window.supExt(id);
const bars = [
{ l: "On-time delivery", v: ext.onTime, sub: "Orders delivered by the promised date" },
{ l: "Fill rate", v: ext.fillRate, sub: "Lines supplied in full, first attempt" },
{ l: "DIFOT", v: ext.difot, sub: "Delivered in full, on time" },
];
function barColor(v) { return v == null ? "var(--border-strong)" : v >= 95 ? "var(--paid)" : v >= 90 ? "var(--hold)" : "var(--void)"; }
return (
Service performance · 12 months
{ext.isNew &&
No delivery history yet — performance builds once orders start flowing.
}
{bars.map(b => (
{b.l} {b.v == null ? "—" : b.v + "%"}
{b.sub}
))}
At a glance
Avg lead time {ext.leadTime} day{ext.leadTime > 1 ? "s" : ""}
Order cut-off {ext.cutoff}
Credits / returns {ext.credits}%
Open backorders {window.SUP_DATA.BACKORDERS[id] || 0}
Channel {ext.channel}
);
}
// ---- Terms & accounts tab ---------------------------------------
function TabTerms({ id }) {
const ext = window.supExt(id);
const accts = window.supAccounts(id);
const s = window.OFFICE_DATA.SUPPLIERS.find(x => x.id === id);
return (
Trading terms Edit
Order priority
{window.supLineLabel(id)}
{window.supIsRanked(id) && (
window.supMoveLine(id, -1)}>
window.supMoveLine(id, 1)}>
)}
Payment terms {ext.terms.payment}
Settlement discount {ext.terms.settlement || "—"}
Volume rebate {ext.terms.rebate || "—"}
Min order / freight {ext.terms.moq}
Returns policy {ext.terms.returns}
Company
ABN {ext.abn}
Address {ext.address}
Website {ext.web}
Phone {s.phone}
Per-store accounts {accts.length}
{accts.map(a => (
{a.code}
{a.store}
{a.account} · since {a.since}
{a.status === "active" ? "Active" : "On hold"}
))}
);
}
// ---- Documents tab ----------------------------------------------
function TabDocs({ id }) {
const ext = window.supExt(id);
const docs = ext.docs || [];
function docBadge(st) {
const m = { current: { c: "badge--paid", l: "Current" }, review: { c: "badge--hold", l: "Review due" }, stale: { c: "badge--void", l: "Out of date" } }[st] || { c: "badge--neutral", l: st };
return
{m.l};
}
return (
Documents & compliance {docs.length} Upload
{docs.map((d, i) => (
{d.name}
{d.type} · updated {d.updated}{d.expires ? " · expires " + d.expires : ""}
{docBadge(d.status)}
))}
);
}
// =================================================================
// PROFILE SHELL
// =================================================================
// -----------------------------------------------------------------
// SUPPLIER DETAIL — LIVE · this pharmacy
// Real supplier profile (passed from the list) + the products that name it as
// primary supplier, fetched from /products?supplierId. Trading history is shown
// as an honest empty state — no spend/PO/invoice data was migrated.
// -----------------------------------------------------------------
function SupplierDetail() {
const { screenState, setScreen, setScreenState } = window.useOffice();
const s = screenState.supplier;
const openProduct = (pid) => { setScreenState((st) => ({ ...st, activeProductId: pid, productFrom: "supplierDetail" })); setScreen("productDetail"); };
const [products, setProducts] = React.useState([]);
const [loadingP, setLoadingP] = React.useState(true);
React.useEffect(() => {
if (!s) return undefined;
let alive = true;
setLoadingP(true);
window.OfficeAPI.listProducts({ supplierId: s.id, limit: 200 })
.then((ps) => { if (alive) { setProducts(ps || []); setLoadingP(false); } })
.catch(() => { if (alive) setLoadingP(false); });
return () => { alive = false; };
}, [s && s.id]);
if (!s) {
return (
No supplier selected.
setScreen("suppliers")}>Back to suppliers
);
}
const money2 = (c) => "$" + (Number(c || 0) / 100).toFixed(2);
const loc = [s.locality, s.region].filter(Boolean).join(", ") || null;
return (
Trading history
No purchase history yet
Spend, orders, invoices and on-time metrics populate as you raise purchase orders — none were migrated from Z.
Products from this supplier
{loadingP ? "loading…" : (products.length >= 200 ? "first 200 of " + s.productCount.toLocaleString("en-AU") : products.length.toLocaleString("en-AU") + " of " + s.productCount.toLocaleString("en-AU"))}
Product
SKU
Sched
Cost
Sell
{products.map((p) => (
openProduct(p.id)}>
{p.name && p.name !== "-" ? p.name : p.sku}
{p.sku}
{p.schedule ? : ""}
{p.costPriceCents == null ? "—" : money2(p.costPriceCents)}
{money2(p.sellPriceCents)}
))}
{!loadingP && products.length === 0 &&
No products link to this supplier.
}
);
}
function SupKV({ k, v, mail }) {
return (
{k}
{v ? (mail ?
{v} : v) : "—"}
);
}
function TabBackorders({ id }) {
const bo = window.supBackorders(id);
if (bo.length === 0) {
return (
No backorders
Every line from this supplier is currently available.
);
}
return (
Lines on backorder {bo.length} Chase supplier
Product Qty Waiting ETA
{bo.map((b, i) => (
{b.product} {b.strength !== "—" ? b.strength : ""}
{b.qty}
{b.waiting}d
{b.eta === "No ETA" ? No ETA : {b.eta} }
))}
);
}
window.OFFICE_SCREENS = Object.assign(window.OFFICE_SCREENS || {}, { supplierDetail: SupplierDetail });