/* ===========================================================
   DTF.uk · Ops — Production board  (design imported from
   claude.ai/design "DTF.uk", wired to the live workflow store)
   Design queue · per-printer print queues · pressing · ready.
   Anything in these lanes is HELD from the dispatch board until
   it clears into "Ready for dispatch".
   =========================================================== */
const { useState: useStateP, useEffect: useEffectP, useMemo: useMemoP } = React;

/* Map the UI lane id -> backend workflow stage (+ whether a printer is kept). */
const UI_TO_STAGE = {
  design:   "design_queue",
  rip:      "rip_queue",
  toprint:  "print_queue",
  printing: "printing",
  printed:  "printed",
  pressing: "press_queue",
  ready:    "ready_dispatch",
};
const PRESS_RE = /hood|tee|t-?shirt|polo|tote|apron|shirt|garment|kit|sweat|cap|hat|bag/i;

/* Which lane an order currently lives in (derived from its workflow stage). */
function uiStageOf(o) {
  const s = o.status, pid = o.printerId;
  if (s === "awaiting_artwork" || s === "design_queue") return "design";
  if (s === "rip_queue") return "rip";
  if (s === "print_queue") return pid ? "printing" : "toprint";
  if (s === "printing") return pid ? "printing" : "toprint";
  if (s === "printed") return "printed";
  if (s === "press_queue") return "pressing";
  if (s === "ready_dispatch") return "ready";
  return null;
}
function needsPressOf(o) { return (o.items || []).some(it => PRESS_RE.test((it.name || "") + " " + (it.variant || ""))); }
// UV items are keyed off the title (same rule as the dispatch board). Split an order's
// linear metres into DTF vs UV so the printing board can show the two workloads apart.
const isUvName = (t) => /\buv\b/i.test(t || "");
function splitMetres(o) {
  let dtf = 0, uv = 0;
  for (const it of (o.items || [])) {
    if (it.removed) continue;
    const m = DTF.itemMetres ? DTF.itemMetres(it) : 0;
    if (isUvName(it.name)) uv += m; else dtf += m;
  }
  return { dtf, uv };
}
const sumSplit = (jobsList) => (jobsList || []).reduce((a, j) => { const s = splitMetres(j.order); return { dtf: a.dtf + s.dtf, uv: a.uv + s.uv }; }, { dtf: 0, uv: 0 });

function dueMeta(d) {
  if (!d) return { label: "", today: false };
  if (d === DTF.TODAY) return { label: "Due today", today: true };
  const days = Math.round((new Date(d) - new Date(DTF.TODAY)) / 86400000);
  if (days === 1) return { label: "Due tomorrow", today: false };
  if (days < 0) return { label: "Overdue", today: true };
  return { label: "Due " + new Date(d).toLocaleDateString("en-GB", { day: "numeric", month: "short" }), today: false };
}

/* Reusable printer manager (Settings page) — full add/rename/enable/smart-delete. */
function PrintersManager() {
  const [, f] = useStateP(0); const bump = () => f(x => x + 1);
  const [name, setName] = useStateP(""); const [busy, setBusy] = useStateP(false);
  const printers = DTF._workflow.printers || [];
  const PRINT_STAGES = ["print_queue", "printing", "printed"];
  const jobCount = id => Object.values(DTF._workflow.orders || {}).filter(o => o.printerId === id && PRINT_STAGES.includes(o.stage)).length;
  async function add() { if (!name.trim()) return; setBusy(true); try { await DTF.addPrinter(name.trim()); setName(""); } finally { setBusy(false); bump(); } }
  async function rename(p) { const n = prompt("Rename printer", p.name); if (n && n.trim() && n.trim() !== p.name) { await DTF.updatePrinter(p.id, { name: n.trim() }); bump(); } }
  async function toggle(p) { await DTF.updatePrinter(p.id, { active: !(p.active !== false) }); bump(); }
  async function del(p) {
    const jobs = jobCount(p.id);
    if (jobs > 0) {
      const others = printers.filter(x => x.id !== p.id);
      if (!others.length) { alert(`Can't delete “${p.name}”: ${jobs} job(s) assigned and no other printer to move them to. Add another printer first.`); return; }
      let tgt = others[0];
      if (others.length > 1) {
        const list = others.map((o, i) => `${i + 1}. ${o.name}`).join("\n");
        const ans = prompt(`“${p.name}” has ${jobs} job(s). Move them to which printer?\n\n${list}\n\nEnter number:`);
        tgt = others[parseInt(ans, 10) - 1]; if (!tgt) return;
      }
      if (!confirm(`Move ${jobs} job(s) to “${tgt.name}” and delete “${p.name}”?`)) return;
      try { await DTF.removePrinter(p.id, tgt.id); } catch (e) { alert(e.message); } bump();
    } else {
      if (!confirm(`Delete printer “${p.name}”?`)) return;
      try { await DTF.removePrinter(p.id); } catch (e) { alert(e.message); } bump();
    }
  }
  return (
    <div className="card">
      <div className="card__head"><Icon name="print" /><h3>Printers</h3></div>
      <div className="card__body">
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {printers.map(p => { const jobs = jobCount(p.id); return (
            <div key={p.id} style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
              <span className="live-dot" style={{ background: p.active === false ? "var(--c-grey)" : "var(--c-green)", animation: "none" }} />
              <span style={{ fontWeight: 600, minWidth: 120 }}>{p.name}</span>
              <span className="dim" style={{ fontSize: 12 }}>{jobs} job{jobs === 1 ? "" : "s"}{p.active === false ? " · disabled" : ""}</span>
              <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
                <button className="btn btn--ghost btn--sm" onClick={() => rename(p)}>Rename</button>
                <button className="btn btn--ghost btn--sm" onClick={() => toggle(p)}>{p.active === false ? "Enable" : "Disable"}</button>
                <button className="btn btn--ghost btn--sm" onClick={() => del(p)}>Delete</button>
              </div>
            </div>
          ); })}
          {!printers.length && <div className="dim" style={{ fontSize: 12.5 }}>No printers yet.</div>}
        </div>
        <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
          <input className="rm-in" placeholder="New printer name…" value={name} onChange={e => setName(e.target.value)} onKeyDown={e => e.key === "Enter" && add()} />
          <button className="btn btn--primary btn--sm" disabled={busy} onClick={add}><Icon name="plus" /> Add printer</button>
        </div>
      </div>
    </div>
  );
}
window.PrintersManager = PrintersManager;

/* Compact artwork strip on a job card — download print files without leaving the board. */
function ArtworkInline({ name, showEmpty }) {
  const [files, setFiles] = useStateP(null);
  useEffectP(() => { let a = true; DTF.artworkFor(name).then(f => a && setFiles(f)); return () => { a = false; }; }, [name]);
  if (!files || !files.length) {
    if (!showEmpty) return null;
    return <div className="jobcard__art jobcard__art--empty"><Icon name="image" /><span className="dim">No artwork uploaded — open the order to add it</span></div>;
  }
  return (
    <div className="jobcard__art">
      <Icon name="image" />
      {files.map(f => (
        <a key={f.id} className="jobcard__artlink" href={DTF.artworkUrl(name, f.id, true)} title={"Download " + f.filename} onClick={e => e.stopPropagation()}>↓ {f.filename}</a>
      ))}
    </div>
  );
}

// Links pulled from the order's line-item properties — Design Preview, Print
// Ready File(s), actual_gang_sheet, etc. Anything whose value is a URL.
function OrderAssets({ o }) {
  const links = []; const seen = new Set();
  (o.items || []).forEach(it => (it.properties || []).forEach(p => {
    const v = String(p.value || "").trim();
    const label = String(p.name || "").replace(/^_+/, "").replace(/_/g, " ").trim() || "file";
    if (/^https?:\/\//i.test(v) && !seen.has(v) && _WANTED_ASSET.test(label)) {
      seen.add(v);
      links.push({ label, url: v });
    }
  }));
  if (!links.length) return null;
  return (
    <div className="jobcard__art">
      <Icon name="external" />
      {links.map((l, i) => (
        <a key={i} className="jobcard__artlink" href={l.url} target="_blank" rel="noopener noreferrer" title={l.label} onClick={e => e.stopPropagation()}>{l.label}</a>
      ))}
    </div>
  );
}

// Is a URL an image we can thumbnail?
const _isImgUrl = u => /\.(png|jpe?g|webp|gif|avif|svg)(\?|#|$)/i.test(u) || /\/preview\//i.test(u);
// Only surface the print-relevant asset fields — skip Edit / Admin Edit / plain
// Preview etc. so we don't thumbnail (and re-render) files nobody needs.
const _WANTED_ASSET = /design.?preview|print.?ready|gang.?sheet/i;
// Build a cached, downscaled thumbnail URL (w = longest side px).
function _thumbUrl(a, w) {
  return a.ext
    ? "/api/thumb?w=" + w + "&src=" + encodeURIComponent(a.download)
    : "/api/thumb?w=" + w + "&order=" + encodeURIComponent(a.order) + "&id=" + encodeURIComponent(a.fid);
}
// Gather an order's files (uploaded artwork + URL-valued line-item properties).
function collectOrderAssets(o, files) {
  const out = []; const seen = new Set();
  (files || []).forEach(f => {
    out.push({ label: f.filename, download: DTF.artworkUrl(o.id, f.id, true), isImage: /^image\//.test(f.contentType || ""), ext: false, order: String(o.id).replace(/^#/, ""), fid: f.id });
  });
  (o.items || []).forEach(it => (it.properties || []).forEach(p => {
    const v = String(p.value || "").trim();
    const label = String(p.name || "").replace(/^_+/, "").replace(/_/g, " ").trim() || "file";
    if (/^https?:\/\//i.test(v) && !seen.has(v) && _WANTED_ASSET.test(label)) {
      seen.add(v);
      out.push({ label, download: v, isImage: _isImgUrl(v), ext: true });
    }
  }));
  return out;
}

function AssetThumb({ a }) {
  if (a.isImage) {
    // No image loaded up front — just a placeholder. The preview (and its
    // thumbnail generation) is fetched on hover, with a loader.
    const big = _thumbUrl(a, 560);
    return (
      <a href={a.download} target="_blank" rel="noopener noreferrer" className="thumb thumb--ph" title={a.label + " — hover to preview"}
        onClick={e => e.stopPropagation()}
        onMouseEnter={e => showPreview(big, e.currentTarget.getBoundingClientRect())}
        onMouseLeave={hidePreview}>
        <Icon name="image" />
      </a>
    );
  }
  return <a href={a.download} target="_blank" rel="noopener noreferrer" className="thumb-link" title={a.label} onClick={e => e.stopPropagation()}>{a.label}</a>;
}

function OrderThumbs({ o }) {
  // Seed from cache synchronously so a remount/re-render doesn't refetch or flash.
  const [files, setFiles] = useStateP(() => (DTF.artworkCached ? DTF.artworkCached(o.id) : null));
  useEffectP(() => {
    if (files != null) return;                 // already have it from cache
    let a = true; DTF.artworkFor(o.id).then(f => a && setFiles(f)); return () => { a = false; };
  }, [o.id]);
  const assets = collectOrderAssets(o, files);
  if (!assets.length) return <span className="muted">—</span>;
  return <div className="thumbs">{assets.map((a, i) => <AssetThumb key={i} a={a} />)}</div>;
}

// Imperative hover preview — a single DOM node, NOT React state, so hovering a
// thumbnail never re-renders the (large) production table. Anchored beside the
// thumbnail and stable; hides when the cursor moves off any thumbnail or on scroll.
let _pvEl = null, _pvImg = null, _pvReqId = 0;
function _ensurePreviewEl() {
  if (_pvEl) return _pvEl;
  _pvEl = document.createElement("div");
  _pvEl.className = "art-preview";
  _pvEl.style.display = "none";
  const sp = document.createElement("div"); sp.className = "spin spin--dark art-preview__spin";
  _pvImg = document.createElement("img");
  _pvEl.appendChild(sp);
  _pvEl.appendChild(_pvImg);
  document.body.appendChild(_pvEl);
  document.addEventListener("mouseover", (e) => {
    if (_pvEl.style.display !== "none" && !(e.target.closest && e.target.closest(".thumb"))) hidePreview();
  });
  document.addEventListener("scroll", () => { if (_pvEl.style.display !== "none") hidePreview(); }, true);
  return _pvEl;
}
function showPreview(url, rect) {
  const el = _ensurePreviewEl();
  // Position the box at a fixed size (loader state) anchored beside the thumb.
  const W = 336, H = 336, pad = 12;
  let left = rect.left - W - pad;
  if (left < pad) left = Math.min(rect.right + pad, window.innerWidth - W - pad);
  let top = rect.top - 40;
  if (top + H + pad > window.innerHeight) top = window.innerHeight - H - pad;
  if (top < pad) top = pad;
  el.style.left = left + "px"; el.style.top = top + "px"; el.style.display = "block";
  // Load the image now (on hover), showing a spinner until it's ready.
  const reqId = ++_pvReqId;
  el.classList.add("is-loading");
  _pvImg.onload = () => { if (reqId === _pvReqId) el.classList.remove("is-loading"); };
  _pvImg.onerror = () => { if (reqId === _pvReqId) hidePreview(); };
  if (_pvImg.getAttribute("src") !== url) _pvImg.src = url;
  else if (_pvImg.complete) el.classList.remove("is-loading"); // already cached
}
function hidePreview() { _pvReqId++; if (_pvEl) _pvEl.style.display = "none"; }

function Production({ go, sub, tab }) {
  const [, force] = useStateP(0); const bump = () => force(x => x + 1);
  // Track the current sub-view so the live listeners (which mount once) can tell
  // where we are without re-subscribing. Print telemetry only matters on Printing.
  const subRef = React.useRef(sub); subRef.current = sub;
  const printLive = () => { if (!subRef.current || subRef.current === "printing") bump(); };
  const [editingId, setEditingId] = useStateP(null);
  const [confirmId, setConfirmId] = useStateP(null);
  const [dragId, setDragId] = useStateP(null);
  const [dropKey, setDropKey] = useStateP(null);
  // Printing sub-tab + pressing selection live HERE (not inside FocusView) so an
  // auto-sync re-render doesn't reset them. Default to the Printing tab.
  // Printing / pressing sub-tabs are driven by the URL (route.tab) so each tab is
  // bookmarkable and survives auto-sync re-renders. Bare route → sensible default.
  const ptab = sub === "printing" ? (tab || "printing") : "printing";       // "toprint" | "printing" | "printed"
  const presstab = sub === "pressing" ? (tab || "ready_press") : "ready_press"; // "awaiting_prints" | "awaiting_garment" | "ready_press"
  const setPtab = (k) => go({ view: "production", sub: "printing", tab: k });
  const setPresstab = (k) => go({ view: "production", sub: "pressing", tab: k });
  // Board search — type an order # or customer to find a job across ALL tabs and
  // see its status (e.g. "did #4652 print yet?"). Lifted so it survives auto-sync.
  const [q, setQ] = useStateP("");
  const [sel, setSel] = useStateP(() => new Set());
  const selBump = () => setSel(new Set(sel));
  // View mode (kanban | table) — persisted, defaults to table.
  const [mode, setMode] = useStateP(() => { try { return localStorage.getItem("dtf_prod_view") || "table"; } catch (e) { return "table"; } });
  useEffectP(() => { try { localStorage.setItem("dtf_prod_view", mode); } catch (e) {} }, [mode]);

  // Pull in any production orders that aren't in the loaded orders page (workflow
  // references them but reloadData only fetches the first page) so the board is
  // complete. Called on open and after every live refresh.
  const fetchMissing = async () => {
    const names = Object.keys((DTF._workflow && DTF._workflow.orders) || {});
    const missing = names.filter(n => !DTF.getOrder(n));
    if (missing.length) await Promise.all(missing.map(n => DTF.fetchOrderByName(n).catch(() => null)));
  };

  // On open, reload the content: refresh orders + workflow (so the board reflects
  // the current state, not whatever was loaded at boot), then pull in any
  // production orders not in the loaded page yet.
  const [loading, setLoading] = useStateP(DTF.orders.length === 0);
  useEffectP(() => {
    let alive = true;
    (async () => {
      try { if (DTF.reloadData) await DTF.reloadData(); } catch (e) {}
      if (!alive) return;
      await fetchMissing();
      if (alive) { setLoading(false); bump(); }
    })();
    return () => { alive = false; };
  }, []);
  // Live-sync pushed fresh data → re-fetch out-of-page production orders (the
  // poller's reload only covers the first page) and re-render the board so it
  // stays current without a manual reload.
  const [syncedAt, setSyncedAt] = useStateP(null);
  useEffectP(() => {
    let alive = true;
    const h = async () => { await fetchMissing(); if (alive) { setSyncedAt(Date.now()); bump(); } };
    window.addEventListener("dtf:refreshed", h);
    // A stage/printer change (from the order page or anywhere) moves an order between
    // lanes — re-render so it drops out of its old list immediately (e.g. assigning a
    // printer sends it from RIP → print queue and it vanishes from the RIP list live).
    window.addEventListener("dtf:workflow", bump);
    // Live printing highlight/% — only re-renders on the Printing view (or the
    // full board), so Design/RIP/Pressing lists don't flicker on every poll.
    window.addEventListener("dtf:print-live", printLive);
    return () => { alive = false; window.removeEventListener("dtf:refreshed", h); window.removeEventListener("dtf:workflow", bump); window.removeEventListener("dtf:print-live", printLive); };
  }, []);
  const [reloading, setReloading] = useStateP(false);
  const doReload = async () => {
    if (reloading) return;
    setReloading(true);
    try {
      if (DTF.reloadData) await DTF.reloadData();
      const names = Object.keys((DTF._workflow && DTF._workflow.orders) || {});
      const missing = names.filter(n => !DTF.getOrder(n));
      if (missing.length) await Promise.all(missing.map(n => DTF.fetchOrderByName(n).catch(() => null)));
    } catch (e) {}
    setReloading(false); bump();
  };

  const printers = DTF._workflow.printers || [];
  const isOnline = p => p.active !== false;

  // Live production jobs derived from real orders (terminal/collected excluded by status).
  const jobs = (DTF.orders || []).map(o => {
    const ui = uiStageOf(o);
    if (!ui) return null;
    const c = DTF.getCustomer(o.customerId);
    return {
      id: o.id, order: o, ui, printerId: o.printerId || null,
      customer: c ? c.name : (o.address || "").split(",")[0] || "—",
      units: (o.items || []).reduce((s, it) => s + (it.qty || 0), 0),
      metres: DTF.orderMetres(o),
      needsPress: needsPressOf(o),
      method: o.method, fulfillBy: o.fulfillBy,
      vip: (o.flags || []).includes("vip"),
    };
  }).filter(Boolean);

  // Move a job to a lane (and optionally a printer); maps UI lane -> backend stage.
  async function move(id, ui, printerId = null) {
    const stage = UI_TO_STAGE[ui];
    await DTF.setOrderStage(id, stage, ui === "printing" ? printerId : null);
    bump();
  }

  // ---- printer CRUD (live workflow store) ----
  async function addPrinter() {
    const before = new Set((DTF._workflow.printers || []).map(p => p.id));
    const n = (DTF._workflow.printers || []).length + 1;
    try { await DTF.addPrinter("DTF Printer " + String(n).padStart(2, "0")); } catch (e) { alert(e.message); }
    const added = (DTF._workflow.printers || []).find(p => !before.has(p.id));
    if (added) setEditingId(added.id);
    bump();
  }
  async function renamePrinter(id, name) { await DTF.updatePrinter(id, { name }); bump(); }
  async function togglePrinter(p) { await DTF.updatePrinter(p.id, { active: !isOnline(p) }); bump(); }
  async function deletePrinter(id) {
    // Send any jobs on this printer back to the unassigned print pile, then delete.
    const onIt = jobs.filter(j => j.printerId === id && j.ui === "printing");
    for (const j of onIt) await DTF.setOrderStage(j.id, "print_queue", null);
    try { await DTF.removePrinter(id); } catch (e) { alert(e.message); }
    setConfirmId(null); bump();
  }

  // ---- drag & drop ----
  function onDrop(e, ui, printerId, disabled) {
    e.preventDefault();
    if (disabled) { setDropKey(null); setDragId(null); return; }
    const id = e.dataTransfer.getData("text/plain") || dragId;
    if (id) move(id, ui, printerId);
    setDropKey(null); setDragId(null);
  }
  const zone = (key, ui, printerId, disabled) => ({
    onDragOver: e => { if (disabled) return; e.preventDefault(); if (dropKey !== key) setDropKey(key); },
    onDrop: e => onDrop(e, ui, printerId, disabled),
  });

  // When an order entered its current stage (for FIFO ordering of the RIP queue).
  const stageTs = id => ((DTF._workflow.orders || {})[id] || {}).updatedAt || 0;

  // ---- groupings ----
  const by = useMemoP(() => {
    const g = { design: [], rip: [], toprint: [], printed: [], pressing: [], ready: [], printers: {} };
    printers.forEach(p => g.printers[p.id] = []);
    jobs.forEach(j => {
      if (j.ui === "printing" && g.printers[j.printerId]) g.printers[j.printerId].push(j);
      else if (j.ui === "printing") g.toprint.push(j); // printer was deleted under it
      else g[j.ui].push(j);
    });
    // Sort every group by order number, newest first — except the RIP queue,
    // which goes oldest order id first (ascending) so it RIPs in arrival order.
    const orderNum = j => parseInt(String(j.id).replace(/[^\d]/g, ""), 10) || 0;
    const byOrderDesc = (a, b) => orderNum(b) - orderNum(a);
    const byOrderAsc = (a, b) => orderNum(a) - orderNum(b);
    g.design.sort(byOrderDesc); g.rip.sort(byOrderAsc); g.toprint.sort(byOrderDesc);
    g.printed.sort(byOrderDesc); g.pressing.sort(byOrderDesc); g.ready.sort(byOrderDesc);
    printers.forEach(p => g.printers[p.id].sort(byOrderDesc));
    return g;
  }, [jobs, printers, force]);

  const onPress = printers.reduce((s, p) => s + by.printers[p.id].length, 0);
  const held = by.design.length + by.rip.length + by.toprint.length + onPress + by.pressing.length;

  // Flat list of jobs in board order, so opening one gives prev/next across the
  // whole production floor on the order page.
  const prodFlat = [
    ...by.design, ...by.rip, ...by.toprint,
    ...printers.flatMap(p => by.printers[p.id]),
    ...by.printed, ...by.pressing, ...by.ready,
  ].map(j => j.id);
  function openJob(o) { DTF.setOrderNav(prodFlat, { source: "Production", route: { view: "production" } }); go({ view: "order", id: o.id }); }

  // ---- job card ----
  function JobCard({ j }) {
    const o = j.order;
    const m = DTF.methodMeta[j.method] || { urg: "rm48" };
    const due = dueMeta(j.fulfillBy);
    const cleared = j.ui === "ready";
    return (
      <div
        className={"jobcard" + (j.vip ? " vip" : "") + (dragId === j.id ? " is-drag" : "")}
        draggable
        onDragStart={e => {
          // Set the payload synchronously so the native drag starts on the FIRST
          // grab; defer the React state update so re-rendering the dragged node
          // doesn't cancel the in-flight drag.
          e.dataTransfer.setData("text/plain", j.id); e.dataTransfer.effectAllowed = "move";
          const id = j.id; setTimeout(() => setDragId(id), 0);
        }}
        onDragEnd={() => { setDragId(null); setDropKey(null); }}
      >
        <div className="jobcard__top">
          <span className={"jobcard__rail urg--" + m.urg} />
          <a className="jobcard__no" href={"#/orders/" + String(o.id).replace(/^#/, "")}
             onClick={e => { if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; e.preventDefault(); openJob(o); }}
             title="Open order (⌘/Ctrl-click for new tab)">{o.id}</a>
          {j.vip && <span className="jobcard__vip" title="VIP">★</span>}
          <span className="jobcard__units">{j.units} pcs</span>
        </div>
        <div className="jobcard__cust">{j.customer}</div>
        <div className="jobcard__svc">
          <span className="svcchip svcchip--print">Print</span>
          {j.needsPress && <span className="svcchip svcchip--press">Press</span>}
          {DTF.artworkCount(o.id) > 0 && <span className="svcchip svcchip--design">Artwork ✓</span>}
        </div>
        <div className="jobcard__due">
          <span className={due.today ? "due-today" : ""}>{due.label}</span>
          {due.label && <span>·</span>}<span>{m.short || j.method}</span>
        </div>

        {j.ui === "rip" ? (
          <>
            <ArtworkInline name={o.id} showEmpty />
            <OrderAssets o={o} />
            <div className="assignrow">
              <div className="assignrow__lbl">RIP & send to printer</div>
              <div className="assignchips">
                {printers.length ? printers.map(p => (
                  <button key={p.id} className="assignchip" disabled={!isOnline(p)}
                    onClick={() => move(j.id, "printing", p.id)} title={isOnline(p) ? "Download artwork → NeoStampa → " + p.name : "Printer offline"}>
                    <span className="pdot" />{p.name}
                  </button>
                )) : <span className="dim" style={{ fontSize: 11 }}>Add a printer to assign</span>}
              </div>
              <div className="riprow__alt">
                <button className="jobback" title="Back to design" onClick={() => move(j.id, "design")}><Icon name="back" /></button>
                <button className="jobbtn jobbtn--ghost" onClick={() => move(j.id, "toprint")}>Print queue (unassigned)</button>
              </div>
            </div>
          </>
        ) : j.ui === "toprint" ? (
          <div className="assignrow">
            <div className="assignrow__lbl">Assign to printer</div>
            <div className="assignchips">
              {printers.length ? printers.map(p => (
                <button key={p.id} className="assignchip" disabled={!isOnline(p)}
                  onClick={() => move(j.id, "printing", p.id)} title={isOnline(p) ? "" : "Printer offline"}>
                  <span className="pdot" />{p.name}
                </button>
              )) : <span className="dim" style={{ fontSize: 11 }}>Add a printer to assign</span>}
            </div>
          </div>
        ) : (
          <>
            <div className="jobcard__foot">
              {cleared
                ? <span className="cleared"><Icon name="check" />Cleared · on dispatch board</span>
                : <span className="hold"><Icon name="clock" />Holds dispatch</span>}
              {j.ui !== "design" && !cleared && (
                <button className="jobback" title="Back to print queue" onClick={() => move(j.id, "toprint")}>
                  <Icon name="back" />
                </button>
              )}
              {j.ui === "design" && (
                <button className="jobbtn" onClick={() => move(j.id, "rip")}>To RIP<Icon name="chevron" /></button>
              )}
              {j.ui === "printing" && (
                <button className="jobbtn" onClick={() => move(j.id, "printed")}>Mark printed</button>
              )}
              {j.ui === "printed" && j.needsPress && (
                <button className="jobbtn jobbtn--ghost" onClick={() => move(j.id, "pressing")}>To press</button>
              )}
              {(j.ui === "printed" || j.ui === "pressing") && (
                <button className="jobbtn" onClick={() => move(j.id, "ready")}><Icon name="truck" />To dispatch</button>
              )}
              {cleared && (
                <button className="jobbtn jobbtn--ghost" onClick={() => move(j.id, j.needsPress ? "pressing" : "toprint")}>Undo</button>
              )}
            </div>
            {(j.ui === "printing" || j.ui === "printed" || j.ui === "pressing" || cleared) && <><ArtworkInline name={o.id} /><OrderAssets o={o} /></>}
          </>
        )}
      </div>
    );
  }

  // ---- table view (grouped by stage, rows drag between stages) ----
  const TBL_STAGES = [
    { ui: "design",   label: "Design",   cls: "badge--violet" },
    { ui: "rip",      label: "RIP",      cls: "badge--teal" },
    { ui: "toprint",  label: "To print", cls: "badge--grey" },
    { ui: "printing", label: "Printing", cls: "badge--blue" },
    { ui: "printed",  label: "Printed",  cls: "badge--teal" },
    { ui: "pressing", label: "Pressing", cls: "badge--amber" },
    { ui: "ready",    label: "Ready",    cls: "badge--mint" },
  ];
  // Drop a job onto a stage group; resolve a printer when entering "printing".
  function moveToStage(id, ui) {
    if (ui === "printing") {
      const j = jobs.find(x => x.id === id);
      const online = printers.filter(isOnline);
      let pid = j && j.printerId;
      if (!pid || !online.find(p => p.id === pid)) pid = online[0] ? online[0].id : null;
      if (!pid) return move(id, "toprint"); // no online printer — hold in queue
      return move(id, "printing", pid);
    }
    move(id, ui);
  }

  function ProductionTable() {
    const NCOLS = 11;
    const rank = m => (DTF.methodMeta[m] || {}).rank != null ? DTF.methodMeta[m].rank : 9;
    const sortRows = list => list.slice().sort((a, b) => {
      const ra = rank(a.method), rb = rank(b.method);
      if (ra !== rb) return ra - rb;
      return (a.fulfillBy || "") < (b.fulfillBy || "") ? -1 : 1;
    });
    // RIP queue goes oldest order id first (arrival order); other lanes by urgency.
    const orderNum = j => parseInt(String(j.id).replace(/[^\d]/g, ""), 10) || 0;
    const sortRipAsc = list => list.slice().sort((a, b) => orderNum(a) - orderNum(b));
    const grouped = TBL_STAGES.map(s => {
      const rows = by[s.ui] === undefined ? jobs.filter(j => j.ui === s.ui) : by[s.ui];
      return { ...s, rows: s.ui === "rip" ? sortRipAsc(rows) : sortRows(rows) };
    });
    const tableFlat = grouped.flatMap(g => g.rows).map(r => r.id);
    const openRow = (o) => { DTF.setOrderNav(tableFlat, { source: "Production", route: { view: "production" } }); go({ view: "order", id: o.id }); };

    function Row({ j }) {
      const o = j.order;
      const m = DTF.methodMeta[j.method] || {};
      const due = dueMeta(j.fulfillBy);
      const cleared = j.ui === "ready";
      const nextLabel = j.ui === "design" ? "To RIP"
        : j.ui === "rip" ? "To print"
        : j.ui === "printing" ? "Mark printed"
        : j.ui === "printed" ? (j.needsPress ? "To press" : "To dispatch")
        : j.ui === "pressing" ? "To dispatch" : null;
      const nextUi = j.ui === "design" ? "rip"
        : j.ui === "rip" ? "toprint"
        : j.ui === "printing" ? "printed"
        : j.ui === "printed" ? (j.needsPress ? "pressing" : "ready")
        : j.ui === "pressing" ? "ready" : null;
      const showPrinter = ["rip", "toprint", "printing"].includes(j.ui);
      const printing = DTF.printingFor && DTF.printingFor(o.id);
      return (
        <tr className={"prodrow" + (j.vip ? " is-vip" : "") + (dragId === j.id ? " is-drag" : "") + (printing ? " is-printing" : "")}
          draggable
          onDragStart={e => { e.dataTransfer.setData("text/plain", j.id); e.dataTransfer.effectAllowed = "move"; const id = j.id; setTimeout(() => setDragId(id), 0); }}
          onDragEnd={() => { setDragId(null); setDropKey(null); }}
          onClick={() => openRow(o)}>
          <td className="tdrag" title="Drag to move stage"><span className="grip" /></td>
          <td><span className="ordno">{o.id}</span>{j.vip && <span className="tvip" title="VIP">★</span>}
            {printing && (
              <div className="prow-prog" title={"Printing on " + (printing.printer || "printer")
                + (printing.totalMetres ? " · " + DTF.fmtMetres(printing.printedMetres) + " / " + DTF.fmtMetres(printing.totalMetres) : "")
                + (printing.etaSec != null ? " · ETA ~" + DTF.fmtDuration(printing.etaSec) : "")}>
                <span className="print-badge__dot" />
                <span className="prow-prog__pct">🖨 {printing.percent != null ? printing.percent + "%" : "…"}</span>
                <span className="prow-prog__bar"><span className="prow-prog__fill" style={{ width: (printing.percent || 0) + "%" }} /></span>
                {printing.etaSec != null && <span className="prow-prog__eta">{DTF.fmtDuration(printing.etaSec)} left</span>}
              </div>
            )}
            {!printing && (() => { const pr = DTF.printResultFor && DTF.printResultFor(o.id); return pr ? <PrintResult rec={pr} compact /> : null; })()}</td>
          <td><span className="cust">{j.customer}</span></td>
          <td className="num" style={{ fontWeight: 600 }}>{j.units}</td>
          <td className="num">{j.metres > 0 ? <span className="m-badge" title="DTF material (linear metres)">{DTF.fmtMetres(j.metres)}</span> : <span className="muted">—</span>}</td>
          <td>
            <span className="svccell">
              <span className="svcchip svcchip--print">Print</span>
              {j.needsPress && <span className="svcchip svcchip--press">Press</span>}
              {DTF.artworkCount(o.id) > 0 && <span className="svcchip svcchip--design">Artwork ✓</span>}
            </span>
          </td>
          <td onClick={e => e.stopPropagation()}><OrderThumbs o={o} /></td>
          <td onClick={e => e.stopPropagation()}>
            {showPrinter ? (
              <select className="psel" value={j.printerId || ""}
                onChange={e => { const v = e.target.value; v ? move(j.id, "printing", v) : move(j.id, "toprint"); }}>
                <option value="">Unassigned</option>
                {printers.map(p => <option key={p.id} value={p.id} disabled={!isOnline(p)}>{p.name}{isOnline(p) ? "" : " — offline"}</option>)}
              </select>
            ) : <span className="muted">—</span>}
          </td>
          <td className="mono" style={{ fontSize: 11.5 }}>{m.short || j.method}</td>
          <td><span className={"tdue" + (due.today ? " is-today" : "")}>{due.label}</span></td>
          <td onClick={e => e.stopPropagation()}>
            {cleared ? <button className="jobbtn jobbtn--ghost" onClick={() => move(j.id, j.needsPress ? "pressing" : "toprint")}>Undo</button>
              : nextLabel ? <button className="jobbtn" onClick={() => move(j.id, nextUi)}>{nextLabel}<Icon name="chevron" /></button>
              : <span className="muted">pick printer →</span>}
          </td>
        </tr>
      );
    }

    return (
      <div className="tbl-wrap">
      <table className="tbl prodtbl">
        <thead>
          <tr>
            <th style={{ width: 28 }} />
            <th>Order</th><th>Customer</th><th className="num" style={{ width: 60 }}>Units</th><th className="num" style={{ width: 66 }}>Metres</th>
            <th>Services</th><th>Files</th><th style={{ width: 168 }}>Printer</th><th>Method</th><th>Due</th>
            <th style={{ width: 150 }}>Action</th>
          </tr>
        </thead>
        {grouped.map(g => {
          const zk = "T:" + g.ui; const isDrop = dropKey === zk;
          return (
            <tbody key={g.ui} className={"prodgrp" + (isDrop ? " is-drop" : "")}
              onDragOver={e => { e.preventDefault(); if (dropKey !== zk) setDropKey(zk); }}
              onDrop={e => { e.preventDefault(); const id = e.dataTransfer.getData("text/plain") || dragId; if (id) moveToStage(id, g.ui); setDropKey(null); setDragId(null); }}>
              <tr className="grphead">
                <td colSpan={NCOLS}>
                  <span className={"grphead__dot " + g.cls} />
                  <span className="grphead__t">{g.label}</span>
                  <span className="grphead__c">{g.rows.length}</span>
                  {(() => { const m = g.rows.reduce((s, j) => s + (j.metres || 0), 0); return m > 0 ? <span className="m-badge m-badge--lg" title="DTF material in this stage (linear metres)">{DTF.fmtMetres(m)}</span> : null; })()}
                  <span className="grphead__note">{g.ui === "ready" ? "Released to dispatch board" : "Held from dispatch"}</span>
                </td>
              </tr>
              {g.ui === "printing"
                ? (g.rows.length
                    ? [...printers.map(p => ({ p, rows: g.rows.filter(j => j.printerId === p.id) })),
                       { p: null, rows: g.rows.filter(j => !printers.find(pp => pp.id === j.printerId)) }]
                        .filter(grp => grp.rows.length)
                        .map(grp => (
                          <React.Fragment key={grp.p ? grp.p.id : "orphan"}>
                            <tr className="prodsub">
                              <td colSpan={NCOLS}>
                                {grp.p
                                  ? <><span className={"prodcol__status " + (isOnline(grp.p) ? "on" : "off")} style={{ display: "inline-block", verticalAlign: "middle", marginRight: 8 }} /><span className="prodsub__t">{grp.p.name}</span>{!isOnline(grp.p) && <span className="muted"> · offline</span>}</>
                                  : <span className="prodsub__t">Unassigned printer</span>}
                                <span className="grphead__c">{grp.rows.length}</span>
                              </td>
                            </tr>
                            {grp.rows.map(j => <Row key={j.id} j={j} />)}
                          </React.Fragment>
                        ))
                    : <tr className="grpempty"><td colSpan={NCOLS}>Drag a job here to move it to printing</td></tr>)
                : (g.rows.length ? g.rows.map(j => <Row key={j.id} j={j} />)
                    : <tr className="grpempty"><td colSpan={NCOLS}>Drag a job here to move it to {g.label.toLowerCase()}</td></tr>)}
            </tbody>
          );
        })}
      </table>
      </div>
    );
  }

  const showSkeleton = reloading || (loading && jobs.length === 0);

  // ---- Focused single-stage lists (sidebar sub-items) ------------------------
  // Each production stage as its own working list. Printing splits into three
  // tabs (to print / printing / ready); Pressing supports select-all + a
  // printable run sheet for the press operator.
  function FocusView() {
    const META = {
      design:   { title: "Design",   sub: "Artwork services — colour-sep, vectorise, names & numbers", icon: "spark",    cls: "badge--violet" },
      rip:      { title: "RIP",      sub: "Oldest first — download artwork, RIP in NeoStampa, assign a printer", icon: "download", cls: "badge--teal" },
      printing: { title: "Printing", sub: "To print → printing → ready", icon: "print", cls: "badge--blue" },
      pressing: { title: "Pressing", sub: "Heat-press onto garments · 160°C · 15s · medium pressure", icon: "shirt", cls: "badge--amber" },
    }[sub] || {};
    // ptab / sel / selBump are lifted to the Production component above so they
    // survive auto-sync re-renders (this function is called inline, not mounted).

    // Which lane's rows this view shows. "printing" jobs live per-printer, so
    // flatten them (plus any orphaned onto deleted printers) for that tab.
    const lane = sub === "printing" ? ptab : sub;
    const printingRows = printers.flatMap(p => by.printers[p.id]);
    // The printing "Ready" tab lists EVERYTHING that's been printed — printed, and
    // anything that moved on (pressing / released to dispatch) — so the operator has a
    // running record of what's printed vs still pending, regardless of next status.
    const printedAll = [...by.printed, ...by.pressing, ...by.ready];

    // Pressing board = customizable (garment) orders, split by two independent
    // signals: is the DTF transfer printed yet, and has the blank garment arrived.
    //  · Awaiting prints  — transfer still on the printing board (order shows on BOTH boards)
    //  · Awaiting garment — transfer printed, blank not received yet
    //  · Ready to press   — transfer printed AND garment received → press & dispatch
    // A customizable order (customization fee applied) is a print+press job. It
    // belongs on the pressing board as soon as it lands — even while it's still
    // "New" and hasn't been routed into the print queue — so it can't slip past
    // the press planner. It only leaves "Awaiting prints" once its transfer prints.
    const TERMINAL_ST = new Set(["dispatched", "delivered", "collected", "refunded", "cancelled"]);
    const isPressOrder = o => (DTF.orderHasCustomFee && DTF.orderHasCustomFee(o)) || needsPressOf(o);
    const toPressJob = o => ({
      id: o.id, order: o, ui: uiStageOf(o) || (o.status === "awaiting_garments" ? "awaiting_garments" : "new"),
      customer: (DTF.getCustomer(o.customerId) || {}).name || (o.address || "").split(",")[0] || "—",
      units: (o.items || []).filter(it => !(DTF.isFeeLine && DTF.isFeeLine(it)) && !it.removed).reduce((s, it) => s + (it.qty || 0), 0),
      metres: DTF.orderMetres(o), needsPress: true, method: o.method, fulfillBy: o.fulfillBy,
      vip: (o.flags || []).includes("vip"),
    });
    // Staged press jobs keep their existing job objects; then add customizable
    // orders not yet in a production stage (New) or parked awaiting garments.
    const stagedPress = jobs.filter(j => (j.needsPress || (DTF.orderHasCustomFee && DTF.orderHasCustomFee(j.order))) && j.ui !== "ready");
    const extraPress = (DTF.orders || [])
      .filter(o => !uiStageOf(o) && o.status !== "held" && isPressOrder(o) && o.fulfil !== "fulfilled" && !TERMINAL_ST.has(o.status))
      .map(toPressJob);
    const garmentJobs = [...stagedPress, ...extraPress];
    const transferPrinted = j => j.ui === "printed" || j.ui === "pressing";
    const garmentIn = j => !!(j.order && j.order.garmentReceived);
    const isHeldGarment = j => j.ui === "awaiting_garments";
    const pressSets = {
      awaiting_prints: garmentJobs.filter(j => !isHeldGarment(j) && !transferPrinted(j)),
      awaiting_garment: garmentJobs.filter(j => isHeldGarment(j) || (transferPrinted(j) && !garmentIn(j))),
      ready_press: garmentJobs.filter(j => !isHeldGarment(j) && transferPrinted(j) && garmentIn(j)),
    };

    // Board-wide status for search results — tells you where a job is regardless
    // of the active tab (the whole point of "search to find out if it printed").
    const statusOf = (j) => {
      if (sub === "pressing") {
        if (isHeldGarment(j)) return { label: "Awaiting garment", cls: "badge--amber" };
        if (transferPrinted(j) && garmentIn(j)) return { label: "Ready to press", cls: "badge--mint" };
        if (transferPrinted(j)) return { label: "Awaiting garment", cls: "badge--amber" };
        return { label: "Awaiting prints", cls: "badge--blue" };
      }
      if (j.ui === "toprint") return { label: "To print", cls: "badge--grey" };
      if (j.ui === "printing") return { label: "Printing", cls: "badge--violet" };
      return { label: "Printed", cls: "badge--teal" }; // printed / pressing / ready
    };
    const boardAll = sub === "printing" ? [...by.toprint, ...printingRows, ...printedAll]
      : sub === "pressing" ? garmentJobs.slice()
      : (by[lane] || []);
    const qq = (q || "").trim().toLowerCase().replace(/^#/, "");
    const searching = !!qq && (sub === "printing" || sub === "pressing");
    const matchQ = (j) => String(j.id).replace(/^#/, "").toLowerCase().includes(qq) || (j.customer || "").toLowerCase().includes(qq);

    const rows = searching ? boardAll.filter(matchQ)
      : sub === "pressing" ? (pressSets[presstab] || []).slice()
      : lane === "printing" ? printingRows.slice()
      : (sub === "printing" && lane === "printed") ? printedAll.slice()
      : (by[lane] || []).slice();
    const focusFlat = rows.map(j => j.id);
    // Back-nav returns to THIS exact list, incl. the active printing/pressing tab.
    const openFocus = (o) => { DTF.setOrderNav(focusFlat, { source: "Production", route: { view: "production", sub, tab: sub === "printing" ? ptab : sub === "pressing" ? presstab : undefined } }); go({ view: "order", id: o.id }); };

    // Printer assign select — Unassigned drops back to the To-print pile.
    const PrinterPick = ({ j }) => (
      <select className="psel" value={j.printerId || ""} onClick={e => e.stopPropagation()}
        onChange={e => { const v = e.target.value; v ? move(j.id, "printing", v) : move(j.id, "toprint"); }}>
        <option value="">Unassigned</option>
        {printers.map(p => <option key={p.id} value={p.id} disabled={!isOnline(p)}>{p.name}{isOnline(p) ? "" : " — offline"}</option>)}
      </select>
    );
    function ActionCell({ j }) {
      // On the pressing board the action depends on the job's state (its press tab).
      // While searching, rows span all tabs, so derive from the job — not presstab.
      if (sub === "pressing") {
        const tabKey = searching
          ? (isHeldGarment(j) ? "awaiting_garment" : transferPrinted(j) ? (garmentIn(j) ? "ready_press" : "awaiting_garment") : "awaiting_prints")
          : presstab;
        if (tabKey === "ready_press") return <button className="jobbtn" onClick={() => move(j.id, "ready")}><Icon name="truck" />Pressed → dispatch</button>;
        if (tabKey === "awaiting_garment") return <span className="muted">Waiting on garment</span>;
        // awaiting_prints — a brand-new order isn't routed yet; offer to send its transfer to print.
        if (j.ui === "new") return <button className="jobbtn" onClick={() => move(j.id, "toprint")}>Send to print<Icon name="chevron" /></button>;
        return <span className="muted">On printing board</span>;
      }
      if (j.ui === "design") return <button className="jobbtn" onClick={() => move(j.id, "rip")}>To RIP<Icon name="chevron" /></button>;
      if (j.ui === "printing") return <button className="jobbtn" onClick={() => move(j.id, "printed")}>Mark printed<Icon name="chevron" /></button>;
      if (j.ui === "printed") return j.needsPress
        ? <button className="jobbtn" onClick={() => move(j.id, "pressing")}>To press<Icon name="chevron" /></button>
        : <button className="jobbtn" onClick={() => move(j.id, "ready")}><Icon name="truck" />To dispatch</button>;
      if (j.ui === "pressing") return <button className="jobbtn" onClick={() => move(j.id, "ready")}><Icon name="truck" />To dispatch</button>;
      if (j.ui === "rip") return <button className="jobbtn jobbtn--ghost" onClick={() => move(j.id, "toprint")}>To print</button>;
      return <span className="muted">—</span>;
    }

    // Garment-received toggle — only on the pressing board. Flips a job between
    // "awaiting garment" and "ready to press" (independent of the print stage).
    async function toggleGarment(j, on) { try { await DTF.setGarmentReceived(j.id, on); } catch (e) {} bump(); }
    const GarmentToggle = ({ j }) => {
      const on = !!(j.order && j.order.garmentReceived);
      return (
        <label className="garment-tog" title={on ? "Garment received — click to undo" : "Mark blank garment as received"}
          style={{ display: "inline-flex", alignItems: "center", gap: 6, cursor: "pointer", fontSize: 12 }}
          onClick={e => e.stopPropagation()}>
          <input type="checkbox" checked={on} onChange={e => toggleGarment(j, e.target.checked)} />
          <span className={on ? "" : "muted"}>{on ? "In" : "Awaiting"}</span>
        </label>
      );
    };

    const showPrinterCol = ["rip", "toprint", "printing"].includes(lane);
    const showCheck = sub === "pressing";
    const showGarment = sub === "pressing";
    const NCOLS = 7 + (showPrinterCol ? 1 : 0) + (showCheck ? 1 : 0) + (showGarment ? 1 : 0);

    // Press run sheet — printable batch summary for the press operator.
    function pressRunSheet() {
      const chosen = sel.size ? rows.filter(j => sel.has(j.id)) : rows;
      if (!chosen.length) { alert("No garments in the pressing queue to print."); return; }
      const esc = s => String(s == null ? "" : s).replace(/[&<>]/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]));
      const body = chosen.map((j, i) => {
        const o = j.order;
        const items = (o.items || [])
          .filter(it => !(DTF.isFeeLine && DTF.isFeeLine(it)) && !it.removed && PRESS_RE.test((it.name || "") + " " + (it.variant || "")))
          .map(it => `<div>${esc(it.qty)}× ${esc(it.name)}${it.variant ? " — " + esc(it.variant) : ""}</div>`).join("");
        const due = dueMeta(j.fulfillBy);
        return `<tr>
          <td class="n">${i + 1}</td>
          <td class="ord">${esc(o.id)}</td>
          <td>${esc(j.customer)}</td>
          <td>${items || '<span class="muted">—</span>'}</td>
          <td class="c">${esc(j.units)}</td>
          <td class="c">${esc(due.label || "")}</td>
          <td class="chk"></td>
        </tr>`;
      }).join("");
      const totalUnits = chosen.reduce((s, j) => s + (j.units || 0), 0);
      const html = `<!doctype html><html><head><meta charset="utf-8"><title>Press run sheet</title>
        <style>
          *{box-sizing:border-box} body{font:13px/1.4 -apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#111;margin:24px}
          h1{font-size:20px;margin:0 0 2px} .meta{color:#555;margin:0 0 14px;font-size:12px}
          .settings{background:#fff7ed;border:1px solid #fed7aa;border-radius:8px;padding:10px 12px;margin:0 0 16px;font-size:13px}
          .settings b{color:#9a3412}
          table{width:100%;border-collapse:collapse} th,td{border:1px solid #d4d4d8;padding:7px 8px;text-align:left;vertical-align:top}
          th{background:#f4f4f5;font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:#3f3f46}
          td.n,td.c{text-align:center;white-space:nowrap} td.ord{font-weight:700} td.chk{width:28px}
          .muted{color:#a1a1aa} tfoot td{font-weight:700;background:#fafafa}
          @media print{body{margin:12mm}}
        </style></head><body>
        <h1>Press run sheet</h1>
        <p class="meta">${chosen.length} order${chosen.length === 1 ? "" : "s"} · ${totalUnits} garment${totalUnits === 1 ? "" : "s"} · DTF.uk Production</p>
        <div class="settings"><b>Press settings:</b> 160°C · 15 seconds · medium pressure · peel warm</div>
        <table>
          <thead><tr><th>#</th><th>Order</th><th>Customer</th><th>Garments</th><th>Qty</th><th>Due</th><th>Done</th></tr></thead>
          <tbody>${body}</tbody>
          <tfoot><tr><td colspan="4">Total</td><td class="c">${totalUnits}</td><td colspan="2"></td></tr></tfoot>
        </table>
        <script>window.onload=function(){window.print();};<\/script>
      </body></html>`;
      const w = window.open("", "_blank");
      if (!w) { alert("Pop-up blocked — allow pop-ups to print the run sheet."); return; }
      w.document.open(); w.document.write(html); w.document.close();
    }

    function Row({ j }) {
      const o = j.order;
      const m = DTF.methodMeta[j.method] || {};
      const due = dueMeta(j.fulfillBy);
      // Print progress/status only on the Printing view.
      const printing = sub === "printing" && DTF.printingFor && DTF.printingFor(o.id);
      const pr = sub === "printing" && !printing && DTF.printResultFor && DTF.printResultFor(o.id);
      return (
        <tr className={"prodrow" + (j.vip ? " is-vip" : "") + (printing ? " is-printing" : "")} onClick={() => openFocus(o)}>
          {showCheck && (
            <td className="chk" onClick={e => e.stopPropagation()}>
              <input type="checkbox" checked={sel.has(j.id)}
                onChange={e => { e.target.checked ? sel.add(j.id) : sel.delete(j.id); selBump(); }} />
            </td>
          )}
          <td><span className="ordno">{o.id}</span>{j.vip && <span className="tvip" title="VIP">★</span>}
            {searching && (() => { const st = statusOf(j); return <span className={"badge " + st.cls} style={{ marginLeft: 6 }}>{st.label}</span>; })()}
            {printing && (
              <div className="prow-prog" title={"Printing on " + (printing.printer || "printer")
                + (printing.totalMetres ? " · " + DTF.fmtMetres(printing.printedMetres) + " / " + DTF.fmtMetres(printing.totalMetres) : "")
                + (printing.etaSec != null ? " · ETA ~" + DTF.fmtDuration(printing.etaSec) : "")}>
                <span className="print-badge__dot" />
                <span className="prow-prog__pct">🖨 {printing.percent != null ? printing.percent + "%" : "…"}</span>
                <span className="prow-prog__bar"><span className="prow-prog__fill" style={{ width: (printing.percent || 0) + "%" }} /></span>
                {printing.etaSec != null && <span className="prow-prog__eta">{DTF.fmtDuration(printing.etaSec)} left</span>}
              </div>
            )}
            {pr && <PrintResult rec={pr} compact />}</td>
          <td><span className="cust">{j.customer}</span></td>
          <td className="num" style={{ fontWeight: 600 }}>{j.units}</td>
          <td className="num">{j.metres > 0 ? <span className="m-badge" title="DTF material (linear metres)">{DTF.fmtMetres(j.metres)}</span> : <span className="muted">—</span>}</td>
          <td onClick={e => e.stopPropagation()}><OrderThumbs o={o} /></td>
          {showPrinterCol && <td onClick={e => e.stopPropagation()}><PrinterPick j={j} /></td>}
          {showGarment && <td onClick={e => e.stopPropagation()}><GarmentToggle j={j} /></td>}
          <td className="mono" style={{ fontSize: 11.5 }}>{m.short || j.method}<span className={"tdue" + (due.today ? " is-today" : "")} style={{ display: "block", fontSize: 11 }}>{due.label}</span></td>
          <td onClick={e => e.stopPropagation()}><ActionCell j={j} /></td>
        </tr>
      );
    }

    const allSel = rows.length > 0 && rows.every(j => sel.has(j.id));
    const toggleAll = () => { if (allSel) rows.forEach(j => sel.delete(j.id)); else rows.forEach(j => sel.add(j.id)); selBump(); };

    return (
      <>
        <div className="prodfocus__head">
          <span className={"grphead__dot " + META.cls} />
          <div className="prodfocus__ttl">
            <div className="lane__t">{META.title}</div>
            <div className="lane__sub">{META.sub}</div>
          </div>
          <span className="lane__count">{rows.length}</span>
          {(sub === "printing" || sub === "pressing") && (
            <span className="prod-search" style={{ display: "flex", alignItems: "center", gap: 6, marginLeft: "auto", background: "var(--surface-2,#f4f5f7)", borderRadius: 8, padding: "3px 8px" }}>
              <Icon name="search" />
              <input value={q} onChange={e => setQ(e.target.value)} placeholder="Find order # or customer…"
                style={{ border: 0, background: "transparent", outline: "none", width: 190, fontSize: 13 }} />
              {q && <button className="lnk" onClick={() => setQ("")} title="Clear" style={{ border: 0, background: "transparent", cursor: "pointer", color: "#8a94a0" }}>✕</button>}
            </span>
          )}
          {sub === "printing" && (
            <span className="kick" style={{ display: "flex", alignItems: "center", gap: 6, marginLeft: (sub === "printing") ? 10 : "auto" }} title="Live-syncs while printers are running">
              <span className="live-dot" /> Auto-sync
            </span>
          )}
          <button className={"btn btn--ghost btn--sm" + ((sub === "printing" || sub === "pressing") ? "" : " right")} onClick={doReload} disabled={reloading} title="Reload now"><Icon name="refresh" /> {reloading ? "Reloading…" : "Reload"}</button>
        </div>

        {searching && (
          <div className="kick" style={{ margin: "0 0 12px" }}>
            {rows.length} result{rows.length === 1 ? "" : "s"} across all tabs for “{q.trim()}”
          </div>
        )}

        {sub === "printing" && !searching && (
          <div className="seg" style={{ marginBottom: 12 }}>
            {[{ k: "toprint", l: "To print", n: by.toprint.length },
              { k: "printing", l: "Printing", n: printers.reduce((s, p) => s + by.printers[p.id].length, 0) },
              { k: "printed", l: "Ready", n: printedAll.length }].map(t => (
              <button key={t.k} className={ptab === t.k ? "is-active" : ""} onClick={() => setPtab(t.k)}>{t.l} <span className="seg__n">{t.n}</span></button>
            ))}
          </div>
        )}

        {sub === "printing" && !searching && (() => {
          const fmt = m => DTF.fmtMetres(m || 0);
          const chip = (label, dtf, uv) => (
            <span className="pm-chip" style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 9px", borderRadius: 8, background: "var(--surface-2,#f4f5f7)", fontSize: 12 }}>
              <b style={{ fontSize: 11, color: "#5b6673" }}>{label}</b>
              {dtf > 0 && <span className="m-badge m-badge--solid" title="DTF material (linear metres)">DTF {fmt(dtf)}</span>}
              {uv > 0 && <span className="m-badge m-badge--uv" title="UV material (linear metres)">UV {fmt(uv)}</span>}
              {dtf <= 0 && uv <= 0 && <span className="dim">—</span>}
            </span>
          );
          const barStyle = { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8, margin: "0 0 12px" };
          // Ready tab: how much material has actually been PRINTED (printed + moved-on).
          if (ptab === "printed") {
            const printedM = sumSplit(printedAll);
            if (printedM.dtf <= 0 && printedM.uv <= 0) return null;
            return <div className="prod-metres" style={barStyle}>{chip("Printed", printedM.dtf, printedM.uv)}</div>;
          }
          // To-print / Printing tabs: the outstanding print workload, split per printer.
          const toPrintM = sumSplit(by.toprint);
          const perPrinter = printers.map(p => ({ name: p.name, ...sumSplit(by.printers[p.id]) }));
          const total = perPrinter.reduce((a, p) => ({ dtf: a.dtf + p.dtf, uv: a.uv + p.uv }), { dtf: toPrintM.dtf, uv: toPrintM.uv });
          if (total.dtf <= 0 && total.uv <= 0) return null;
          return (
            <div className="prod-metres" style={barStyle}>
              {chip("Total", total.dtf, total.uv)}
              <span style={{ color: "#c9c9c4" }}>·</span>
              {chip("To print", toPrintM.dtf, toPrintM.uv)}
              {perPrinter.map(p => <span key={p.name}>{chip(p.name, p.dtf, p.uv)}</span>)}
            </div>
          );
        })()}

        {sub === "pressing" && !searching && (
          <div className="seg" style={{ marginBottom: 12 }}>
            {[{ k: "ready_press", l: "Ready to press", n: pressSets.ready_press.length },
              { k: "awaiting_garment", l: "Awaiting garment", n: pressSets.awaiting_garment.length },
              { k: "awaiting_prints", l: "Awaiting prints", n: pressSets.awaiting_prints.length }].map(t => (
              <button key={t.k} className={presstab === t.k ? "is-active" : ""} onClick={() => setPresstab(t.k)}>{t.l} <span className="seg__n">{t.n}</span></button>
            ))}
          </div>
        )}

        {sub === "pressing" && !searching && (
          <div className="toolbar" style={{ marginBottom: 12 }}>
            <button className="btn btn--ghost btn--sm" onClick={toggleAll} disabled={!rows.length}>{allSel ? "Clear selection" : "Select all"}</button>
            <span className="kick">{sel.size ? sel.size + " selected" : "Print a run sheet for the press operator"}</span>
            <button className="btn btn--dark btn--sm right" onClick={pressRunSheet} disabled={!rows.length}><Icon name="print" /> Print run sheet{sel.size ? " (" + sel.size + ")" : ""}</button>
          </div>
        )}

        {showSkeleton ? <SkeletonRows rows={8} /> : (
          <div className="tbl-wrap">
            <table className="tbl prodtbl">
              <thead>
                <tr>
                  {showCheck && <th className="chk" style={{ width: 34 }}><input type="checkbox" checked={allSel} onChange={toggleAll} title="Select all" /></th>}
                  <th>Order</th><th>Customer</th><th className="num" style={{ width: 60 }}>Units</th><th className="num" style={{ width: 66 }}>Metres</th>
                  <th>Files</th>{showPrinterCol && <th style={{ width: 168 }}>Printer</th>}{showGarment && <th style={{ width: 104 }}>Garment</th>}<th>Method · Due</th><th style={{ width: 150 }}>Action</th>
                </tr>
              </thead>
              <tbody>
                {rows.length ? rows.map(j => <Row key={j.id} j={j} />)
                  : <tr className="grpempty"><td colSpan={NCOLS}>Nothing here right now.</td></tr>}
              </tbody>
            </table>
          </div>
        )}
      </>
    );
  }

  if (sub) {
    return (
      <div className="prod prodfocus fade-in">
        {/* live print progress panel only on the Printing view */}
        {(sub === "printing" && DTF._printing && DTF._printing.length > 0) && (
          <div className="prod-live">
            {DTF._printing.map(p => <PrintProgress key={p.order} orderId={p.order} />)}
          </div>
        )}
        {FocusView()}
      </div>
    );
  }

  return (
    <div className="prod fade-in">
      {/* live print progress — one card per printer currently printing (top widget) */}
      {(DTF._printing && DTF._printing.length > 0) && (
        <div className="prod-live">
          {DTF._printing.map(p => <PrintProgress key={p.order} orderId={p.order} />)}
        </div>
      )}
      {/* stats */}
      <div className="stats">
        <div className="stat"><div className="stat__k">In design</div><div className="stat__v">{by.design.length}</div><div className="stat__d">Artwork services</div></div>
        <div className="stat"><div className="stat__k">To RIP</div><div className="stat__v">{by.rip.length}</div><div className="stat__d">Download → NeoStampa</div></div>
        <div className="stat"><div className="stat__k">On press</div><div className="stat__v">{onPress}</div><div className="stat__d">{printers.filter(isOnline).length} printers online</div></div>
        <div className="stat"><div className="stat__k">Pressing</div><div className="stat__v">{by.pressing.length}</div><div className="stat__d">Heat-press station</div></div>
        <div className="stat stat--accent"><div className="stat__k">Cleared for dispatch</div><div className="stat__v">{by.ready.length}</div><div className="stat__d">{held} still held</div></div>
      </div>

      <div className="toolbar">
        <div className="seg">
          <button className={mode === "table" ? "is-active" : ""} onClick={() => setMode("table")}>Table</button>
          <button className={mode === "board" ? "is-active" : ""} onClick={() => setMode("board")}>Kanban board</button>
        </div>
        <span className="kick" style={{ marginLeft: 4 }}>Design → RIP → Print → Press · {held} held from dispatch</span>
        <span className="kick right" style={{ display: "flex", alignItems: "center", gap: 6 }} title="Auto-refreshes when the board changes server-side">
          <span className="live-dot" /> Auto-sync{syncedAt && window.fmtEventTime ? " · " + window.fmtEventTime(syncedAt) : ""}
        </span>
        <button className="btn btn--ghost btn--sm" onClick={doReload} disabled={reloading} title="Reload production now"><Icon name="refresh" /> {reloading ? "Reloading…" : "Reload"}</button>
      </div>

      {showSkeleton ? <SkeletonRows rows={9} /> : mode === "table" ? <ProductionTable /> : <>
      <div className="prodnote">
        <Icon name="clock" />
        <span><b>{held} job{held === 1 ? "" : "s"} held from the dispatch board</b> while in Design, Print or Pressing. A job becomes available to dispatch once it reaches <b>Ready for dispatch</b>.</span>
      </div>

      {/* DESIGN QUEUE */}
      <div className="lane">
        <div className="lane__head">
          <span className="lane__ic lane__ic--design"><Icon name="spark" /></span>
          <div>
            <div className="lane__t">Design queue</div>
            <div className="lane__sub">Artwork services — colour-sep, vectorise, names &amp; numbers</div>
          </div>
          <span className="lane__count">{by.design.length}</span>
        </div>
        <div className={"lane__track" + (dropKey === "design" ? " is-drop" : "")} {...zone("design", "design", null, false)}>
          {by.design.length ? by.design.map(j => <JobCard key={j.id} j={j} />)
            : <div className="lane__empty">No artwork jobs. Drag a job here to send it to design.</div>}
        </div>
      </div>

      {/* RIP QUEUE — flat FIFO list: download artwork → NeoStampa → assign a printer */}
      <div className="lane">
        <div className="lane__head">
          <span className="lane__ic" style={{ background: "var(--c-teal-w)", color: "var(--c-teal)" }}><Icon name="download" /></span>
          <div>
            <div className="lane__t">RIP queue</div>
            <div className="lane__sub">Oldest first — download artwork, RIP in NeoStampa, then assign a printer</div>
          </div>
          <span className="lane__count">{by.rip.length}</span>
        </div>
        <div className={"lane__track" + (dropKey === "rip" ? " is-drop" : "")} {...zone("rip", "rip", null, false)}>
          {by.rip.length ? by.rip.map(j => <JobCard key={j.id} j={j} />)
            : <div className="lane__empty">Nothing waiting to RIP. Send jobs here from Design.</div>}
        </div>
      </div>

      {/* PRINTERS */}
      <div className="prodbar">
        <h2><Icon name="print" />Printers &amp; print queues</h2>
        <div className="right">
          <button className="btn btn--dark btn--sm" onClick={addPrinter}><Icon name="plus" />Add printer</button>
        </div>
      </div>

      <div className="printers">
        {/* Unassigned / To-print pile */}
        <div className="prodcol prodcol--unassigned">
          <div className="prodcol__head">
            <span className="lane__ic lane__ic--press" style={{ background: "var(--c-blue-w)", color: "var(--c-blue)" }}><Icon name="box" /></span>
            <div className="prodcol__name">To print<small>Unassigned</small></div>
            <span className="prodcol__cnt">{by.toprint.length}</span>
          </div>
          <div className={"prodcol__body" + (dropKey === "toprint" ? " is-drop" : "")} {...zone("toprint", "toprint", null, false)}>
            {by.toprint.length ? by.toprint.map(j => <JobCard key={j.id} j={j} />)
              : <div className="prodcol__empty">Nothing waiting</div>}
          </div>
        </div>

        {/* one column per printer */}
        {printers.map(p => {
          const list = by.printers[p.id]; const online = isOnline(p); const dk = "P:" + p.id;
          return (
            <div key={p.id} className="prodcol">
              <div className="prodcol__head">
                <span className={"prodcol__status " + (online ? "on" : "off")} title={online ? "Online" : "Offline"} />
                {editingId === p.id ? (
                  <div className="prodcol__rename">
                    <input autoFocus defaultValue={p.name}
                      onBlur={e => { const v = e.target.value.trim(); if (v && v !== p.name) renamePrinter(p.id, v); setEditingId(null); }}
                      onKeyDown={e => { if (e.key === "Enter") e.target.blur(); if (e.key === "Escape") setEditingId(null); }} />
                  </div>
                ) : confirmId === p.id ? (
                  <div className="prodcol__confirm">
                    Delete printer?
                    <span className="sp">
                      <button className="iconmini danger" onClick={() => deletePrinter(p.id)} title="Confirm — jobs go back to To print"><Icon name="check" /></button>
                      <button className="iconmini" onClick={() => setConfirmId(null)} title="Cancel"><Icon name="x" /></button>
                    </span>
                  </div>
                ) : (
                  <>
                    <div className="prodcol__name" onDoubleClick={() => setEditingId(p.id)} title="Double-click to rename">{p.name}</div>
                    <span className="prodcol__cnt">{list.length}</span>
                    <div className="prodcol__menu">
                      <button className="iconmini" onClick={() => togglePrinter(p)} title={online ? "Set offline" : "Set online"}><Icon name={online ? "check" : "clock"} /></button>
                      <button className="iconmini" onClick={() => setEditingId(p.id)} title="Rename"><Icon name="doc" /></button>
                      <button className="iconmini danger" onClick={() => setConfirmId(p.id)} title="Delete"><Icon name="x" /></button>
                    </div>
                  </>
                )}
              </div>
              <div className={"prodcol__body" + (!online ? " is-blocked" : "") + (dropKey === dk ? " is-drop" : "")} {...zone(dk, "printing", p.id, !online)}>
                {!online && !list.length
                  ? <div className="prodcol__paused">Printer offline — set online to assign jobs</div>
                  : list.length ? list.map(j => <JobCard key={j.id} j={j} />)
                    : <div className="prodcol__empty">Drop jobs here to print</div>}
              </div>
            </div>
          );
        })}
      </div>

      {/* PRINTED — done printing, waiting to press or dispatch */}
      <div className="lane" style={{ marginTop: 18 }}>
        <div className="lane__head">
          <span className="lane__ic" style={{ background: "var(--c-teal-w)", color: "var(--c-teal)" }}><Icon name="check" /></span>
          <div>
            <div className="lane__t">Printed</div>
            <div className="lane__sub">Off the printer — send garments to press, everything else to dispatch</div>
          </div>
          <span className="lane__count">{by.printed.length}</span>
        </div>
        <div className={"lane__track" + (dropKey === "printed" ? " is-drop" : "")} {...zone("printed", "printed", null, false)}>
          {by.printed.length ? by.printed.map(j => <JobCard key={j.id} j={j} />)
            : <div className="lane__empty">Nothing printed yet. Mark jobs printed from the print queues.</div>}
        </div>
      </div>

      {/* PRESSING QUEUE */}
      <div className="lane" style={{ marginTop: 18 }}>
        <div className="lane__head">
          <span className="lane__ic lane__ic--press"><Icon name="shirt" /></span>
          <div>
            <div className="lane__t">Pressing queue</div>
            <div className="lane__sub">Heat-press onto garments — 160°C · 15s · medium pressure</div>
          </div>
          <span className="lane__count">{by.pressing.length}</span>
        </div>
        <div className={"lane__track" + (dropKey === "pressing" ? " is-drop" : "")} {...zone("pressing", "pressing", null, false)}>
          {by.pressing.length ? by.pressing.map(j => <JobCard key={j.id} j={j} />)
            : <div className="lane__empty">No garments waiting to press.</div>}
        </div>
      </div>

      {/* READY FOR DISPATCH */}
      <div className="lane">
        <div className="lane__head">
          <span className="lane__ic lane__ic--ready"><Icon name="truck" /></span>
          <div>
            <div className="lane__t">Ready for dispatch</div>
            <div className="lane__sub">Production complete — released to the dispatch board</div>
          </div>
          <span className="lane__count">{by.ready.length}</span>
        </div>
        <div className={"lane__track" + (dropKey === "ready" ? " is-drop" : "")} {...zone("ready", "ready", null, false)}>
          {by.ready.length ? by.ready.map(j => <JobCard key={j.id} j={j} />)
            : <div className="lane__empty">Clear jobs through production and they land here, ready to ship.</div>}
        </div>
      </div>
      </>}
    </div>
  );
}
window.Production = Production;
window.ProductionBoard = Production;
