/* Relay — shared UI components */
const { PLATFORMS, STATUS, FLOW_LABELS } = window.RelayData;
// ---------- helpers ----------
function fmtMoney(n) {
return n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function minsAgo(ts, now) {
return Math.max(0, Math.floor((now - ts) / 60000));
}
function timeAgo(ts, now) {
const m = minsAgo(ts, now);
if (m < 1) return "şimdi";
if (m < 60) return m + " dk önce";
const h = Math.floor(m / 60);
return h + "sa " + (m % 60) + "dk önce";
}
function clockTime(ts) {
return new Date(ts).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
}
function itemCount(o) { return o.items.reduce((s, it) => s + it.qty, 0); }
// ---------- atoms ----------
function PlatformBadge({ pid, withName, size = 26 }) {
const p = PLATFORMS[pid];
return (
{p.short}
{withName && {p.name}}
);
}
// Prep timer: counts elapsed since accepted; flags late vs prepMins target
function StatusPill({ status, type }) {
const s = STATUS[status];
const label = type ? FLOW_LABELS[type][status] || s.label : s.label;
return (
{label}
);
}
function TypeIcon({ type, size = 14 }) {
return (
{type === "food" ? : }
);
}
function PrepTimer({ order, now, compact }) {
if (order.status === "completed" || order.status === "cancelled") return null;
const base = order.acceptedAt || order.placedAt;
const elapsedMs = Math.max(0, now - base);
const elapsedMin = Math.floor(elapsedMs / 60000);
const elapsedSec = Math.floor(elapsedMs / 1000) % 60;
const late = elapsedMin >= order.prepMins;
const color = late ? "var(--cancelled)" : order.status === "new" ? "var(--new)" : "var(--text-2)";
const mm = String(elapsedMin).padStart(2, "0");
const ss = String(elapsedSec).padStart(2, "0");
return (
{mm}:{ss}
{late && !compact && GECİKTİ}
);
}
function Btn({ kind = "default", children, onClick, small, full, style, title }) {
const base = {
display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6,
fontFamily: "var(--font)", fontWeight: 600, fontSize: small ? 12.5 : 13.5,
padding: small ? "6px 11px" : "9px 14px", borderRadius: "var(--radius-sm)",
border: "1px solid transparent", width: full ? "100%" : "auto", transition: "all .12s ease",
whiteSpace: "nowrap", ...style,
};
const kinds = {
default: { background: "var(--surface)", color: "var(--text)", borderColor: "var(--border-strong)" },
primary: { background: "var(--graphite)", color: "#fff" },
accept: { background: "var(--ready)", color: "#fff" },
reject: { background: "var(--surface)", color: "var(--cancelled)", borderColor: "#f0c9c9" },
ghost: { background: "transparent", color: "var(--text-2)" },
};
return (
);
}
// next-step CTA based on status
function actionFor(order) {
const f = FLOW_LABELS[order.type];
switch (order.status) {
case "preparing": return { to: "ready", label: f.readyCta };
case "ready": return { to: "completed", label: f.doneCta };
default: return null;
}
}
// ---------- Order Card (grid + kanban) ----------
function OrderCard({ order, now, onOpen, onAccept, onReject, onAdvance, dense }) {
const isNew = order.status === "new";
const act = actionFor(order);
const shown = order.items.slice(0, dense ? 2 : 3);
const extra = order.items.length - shown.length;
return (
onOpen(order)} className={order._flash ? "flash-new" : ""}
style={{
background: "var(--surface)", border: "1px solid var(--border)",
borderLeft: `3px solid ${isNew ? "var(--new)" : "transparent"}`,
borderRadius: "var(--radius)", padding: dense ? "11px 13px" : "13px 15px",
boxShadow: "var(--shadow-sm)", cursor: "pointer", transition: "box-shadow .12s, transform .12s",
display: "flex", flexDirection: "column", gap: dense ? 8 : 10,
animation: isNew ? "pulseRing 2.2s ease-out 1" : "none",
}}
onMouseEnter={(e) => { e.currentTarget.style.boxShadow = "var(--shadow-md)"; e.currentTarget.style.transform = "translateY(-1px)"; }}
onMouseLeave={(e) => { e.currentTarget.style.boxShadow = "var(--shadow-sm)"; e.currentTarget.style.transform = "none"; }}>
{/* header */}
{/* customer + meta */}
{order.customer}
{order.fulfillment}
·
{order.area}
{isNew ?
:
}
{/* items */}
{shown.map((it, i) => (
{it.qty}×
{it.name}
))}
{extra > 0 &&
+{extra} ürün daha
}
{/* footer */}
{order.currency} {fmtMoney(order.total)}
{timeAgo(order.placedAt, now)}
{!isNew && !act && order.status === "completed" &&
}
{/* actions */}
{isNew && (
onReject(order)}>Reddet
onAccept(order)}>Kabul Et
)}
{act && (
onAdvance(order)}>{act.label}
)}
);
}
// ---------- Order Row (list view) ----------
function OrderRow({ order, now, onOpen, onAccept, onReject, onAdvance, dense }) {
const isNew = order.status === "new";
const act = actionFor(order);
const pad = dense ? "8px 16px" : "12px 16px";
return (
onOpen(order)} className={order._flash ? "flash-new" : ""}
style={{
display: "grid",
gridTemplateColumns: "150px 110px 1fr 70px 130px 140px 210px",
alignItems: "center", gap: 14, padding: pad,
background: "var(--surface)", borderBottom: "1px solid var(--border)",
borderLeft: `3px solid ${isNew ? "var(--new)" : "transparent"}`,
cursor: "pointer", transition: "background .1s",
}}
onMouseEnter={(e) => { e.currentTarget.style.background = "var(--surface-2)"; }}
onMouseLeave={(e) => { e.currentTarget.style.background = "var(--surface)"; }}>
{order.channelRef}
{order.customer}
{order.fulfillment} · {order.area}
{itemCount(order)} ✕
{order.currency} {fmtMoney(order.total)}
{isNew ?
:
}
{clockTime(order.placedAt)}
{isNew && <>
onReject(order)}>
onAccept(order)}>Kabul Et
>}
{act && onAdvance(order)}>{act.label}}
{!isNew && !act && {order.status === "completed" ? "Tamamlandı" : ""}}
);
}
// ---------- KPI tile ----------
function Kpi({ label, value, sub, accent, icon }) {
return (
{icon}{label}
{value}
{sub && {sub}}
);
}
Object.assign(window, {
fmtMoney, minsAgo, timeAgo, clockTime, itemCount, actionFor,
PlatformBadge, StatusPill, TypeIcon, PrepTimer, Btn, OrderCard, OrderRow, Kpi,
});