// ===========================================================================
// LEON LOGISTICS — a live warehouse, on the stock the Hub already holds
// ===========================================================================
// Studied against Cin7's warehouse product. What it does that the Hub did not:
// it tracks the PACKAGE, not only the material. The Hub could say there are 68
// boxes of adhesive; it could not say which crate they are in, which rack that
// crate is on, or who moved it last — which is what a warehouse actually runs
// on and what a label is for.
//
// Everything here reads the EXISTING records — warehouses, warehouseMaterials,
// inventoryTransactions, materialAllocations. `warehousePackages` is the one
// new collection, and it points at those rather than copying them.

const LOG_SECTIONS = [
  { key: 'dashboard', label: 'Dashboard', icon: '📊', group: 'Warehouse' },
  { key: 'packages', label: 'Packages', icon: '📦', group: 'Warehouse' },
  { key: 'scan', label: 'Scan / Look up', icon: '🔍', group: 'Warehouse' },
  { key: 'labels', label: 'Print Labels', icon: '🏷️', group: 'Warehouse' },
  { key: 'receiving', label: 'Receiving', icon: '📥', group: 'Movements' },
  { key: 'picking', label: 'Picking & Staging', icon: '📤', group: 'Movements' },
  { key: 'moves', label: 'Movement Log', icon: '🧾', group: 'Movements' },
  { key: 'stock', label: 'Stock on Hand', icon: '🗄️', group: 'Stock' },
  { key: 'bins', label: 'Bins & Locations', icon: '🧭', group: 'Stock' },
];

function logPackages(ctx) { return ctx.warehousePackages || []; }
function logWarehouses(ctx) { return ctx.warehouses || []; }
function logSet(ctx, fn) { ctx.setWarehousePackages(prev => fn([...(prev || [])])); }

// The next code in the sequence. A warehouse code has to be short enough to
// read off a label across a rack and unique enough never to be reused, so it is
// the year and a running number rather than a random id.
function logNextCode(ctx) {
  const yr = String(new Date().getFullYear()).slice(-2);
  const used = logPackages(ctx)
    .map(p => (String(p.code || '').match(new RegExp(`^LP${yr}-(\\d+)$`)) || [])[1])
    .filter(Boolean).map(Number);
  const n = (used.length ? Math.max(...used) : 0) + 1;
  return `LP${yr}-${String(n).padStart(4, '0')}`;
}

function LogisticsSoftware({ ctx }) {
  // A link can say which screen, the same as the other tools.
  const [section, setSection] = useState(() =>
    (typeof swBootParam === 'function' && swBootParam('section')) || 'dashboard');
  const editable = ctx.canEdit('warehouse') !== false;   // the Inventory module key is 'warehouse'
  const pkgs = logPackages(ctx);
  const onHand = pkgs.filter(p => WAREHOUSE_ON_HAND_STATUSES.indexOf(p.status) >= 0);

  return (
    // The chrome comes from the router, not from here — a module that renders
    // its own masthead ends up with two.
    <SoftwareRail swKey="logistics" sections={LOG_SECTIONS} active={section} onSelect={setSection}
        status={`${pkgs.length} packages · ${onHand.length} on hand · ${logWarehouses(ctx).length} warehouses`}>
        {section === 'dashboard' && <LogDashboard ctx={ctx} onGo={setSection} />}
        {section === 'packages' && <LogPackages ctx={ctx} editable={editable} />}
        {section === 'scan' && <LogScan ctx={ctx} editable={editable} />}
        {section === 'labels' && <LogLabels ctx={ctx} />}
        {section === 'receiving' && <LogReceiving ctx={ctx} editable={editable} />}
        {section === 'picking' && <LogPicking ctx={ctx} editable={editable} />}
        {section === 'moves' && <LogMovements ctx={ctx} />}
        {section === 'stock' && <LogStock ctx={ctx} />}
        {section === 'bins' && <LogBins ctx={ctx} />}
    </SoftwareRail>
  );
}

function LogKpi({ label, value, sub, tone }) {
  return (
    <div className="rounded-lg border border-[var(--leon-line)] bg-white p-3">
      <div className="text-[10px] uppercase tracking-wide text-[var(--leon-black)]/45">{label}</div>
      <div className={`text-xl font-bold ${tone || ''}`}>{value}</div>
      {sub && <div className="text-[11px] text-[var(--leon-black)]/45">{sub}</div>}
    </div>
  );
}

function LogDashboard({ ctx, onGo }) {
  const pkgs = logPackages(ctx);
  const by = st => pkgs.filter(p => p.status === st).length;
  const noLoc = pkgs.filter(p => WAREHOUSE_ON_HAND_STATUSES.indexOf(p.status) >= 0 && !p.bin && !p.rack).length;
  const mats = ctx.warehouseMaterials || [];
  const low = mats.filter(m => qnum(m.reorderPoint) > 0 && qnum(m.currentStock) <= qnum(m.reorderPoint)).length;
  return (
    <div className="space-y-4">
      <div>
        <h3 className="font-bold">📊 Warehouse</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          What is here, what is moving and what nobody can find. A package with no bin is the one that
          gets looked for twice.
        </p>
      </div>
      <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
        <LogKpi label="Packages on hand" value={pkgs.filter(p => WAREHOUSE_ON_HAND_STATUSES.indexOf(p.status) >= 0).length}
          sub={`${pkgs.length} tracked in all`} />
        <LogKpi label="Expected" value={by('Expected')} sub="not yet received" />
        <LogKpi label="Staged to load" value={by('Staged') + by('Picked')} sub="picked or staged" />
        <LogKpi label="No location" value={noLoc} sub="on hand, no bin set"
          tone={noLoc ? 'text-[var(--leon-red)]' : ''} />
      </div>
      <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
        <LogKpi label="Materials" value={mats.length} sub="stock items" />
        <LogKpi label="At or below reorder" value={low} tone={low ? 'text-amber-700' : ''} />
        <LogKpi label="Warehouses" value={logWarehouses(ctx).length} />
        <LogKpi label="Movements logged" value={pkgs.reduce((a, p) => a + (p.movements || []).length, 0)} />
      </div>
      <div className="flex flex-wrap gap-2">
        <Button size="sm" onClick={() => onGo('packages')}>📦 Packages</Button>
        <Button size="sm" variant="outline" onClick={() => onGo('scan')}>🔍 Scan a code</Button>
        <Button size="sm" variant="outline" onClick={() => onGo('labels')}>🏷️ Print labels</Button>
        <Button size="sm" variant="outline" onClick={() => onGo('receiving')}>📥 Receive</Button>
      </div>
    </div>
  );
}

// ── Packages ───────────────────────────────────────────────────────────────
function LogPackageEditor({ ctx, pkg, editable, onClose }) {
  const whs = logWarehouses(ctx);
  const mats = ctx.warehouseMaterials || [];
  const upd = f => logSet(ctx, l => l.map(p => p.id === pkg.id ? { ...p, ...f } : p));
  function move(to, status, note) {
    const from = warehousePackageLocation(pkg, whs);
    logSet(ctx, l => l.map(p => p.id === pkg.id ? {
      ...p, ...to, status: status || p.status,
      movements: [makeWarehouseMovement({ from, to: to.__label || '', status: status || p.status,
        by: ctx.currentUserName, note: note || '' }), ...(p.movements || [])],
    } : p));
  }
  const qr = typeof LeonQR !== 'undefined' ? LeonQR.toDataUrl(warehousePackageQr(pkg), { size: 150 }) : '';
  return (
    <Modal open onClose={onClose} size="lg" title={`${pkg.code} — ${pkg.name || pkg.kind}`}>
      <div className="grid sm:grid-cols-[150px_1fr] gap-4">
        <div>
          {qr && <img src={qr} alt={`QR for ${pkg.code}`} className="w-full border border-[var(--leon-line)] rounded" />}
          <div className="text-center font-mono text-sm font-bold mt-1">{pkg.code}</div>
          <div className="text-center text-[10px] text-[var(--leon-black)]/45">
            Scans to this package in the Hub
          </div>
        </div>
        <div className="space-y-3">
          <div className="grid sm:grid-cols-2 gap-3">
            <Field label="Name / what it is">
              <TextInput value={pkg.name} disabled={!editable} onChange={e => upd({ name: e.target.value })} />
            </Field>
            <Field label="Kind">
              <Select value={pkg.kind} disabled={!editable} onChange={e => upd({ kind: e.target.value })}>
                {WAREHOUSE_PACKAGE_KINDS.map(k => <option key={k}>{k}</option>)}
              </Select>
            </Field>
            <Field label="Status">
              <Select value={pkg.status} disabled={!editable}
                onChange={e => move({ __label: warehousePackageLocation(pkg, whs) }, e.target.value, 'Status changed.')}>
                {WAREHOUSE_PACKAGE_STATUSES.map(k => <option key={k}>{k}</option>)}
              </Select>
            </Field>
            <Field label="Quantity in it">
              <TextInput type="number" value={pkg.qty} disabled={!editable}
                onChange={e => upd({ qty: Number(e.target.value) || 0 })} />
            </Field>
          </div>
          <div className="rounded-lg border border-[var(--leon-line)] p-3">
            <div className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--leon-black)]/55 mb-2">
              Where it is
            </div>
            <div className="grid sm:grid-cols-4 gap-2">
              <Field label="Warehouse">
                <Select value={pkg.warehouseId || ''} disabled={!editable}
                  onChange={e => upd({ warehouseId: e.target.value || null })}>
                  <option value="">— none —</option>
                  {whs.map(w => <option key={w.id} value={w.id}>{w.name}</option>)}
                </Select>
              </Field>
              <Field label="Zone"><TextInput value={pkg.zone} disabled={!editable} onChange={e => upd({ zone: e.target.value })} /></Field>
              <Field label="Rack"><TextInput value={pkg.rack} disabled={!editable} onChange={e => upd({ rack: e.target.value })} /></Field>
              <Field label="Bin"><TextInput value={pkg.bin} disabled={!editable} onChange={e => upd({ bin: e.target.value })} /></Field>
            </div>
          </div>
          <div className="grid sm:grid-cols-2 gap-3">
            <Field label="Project">
              <Select value={pkg.projectId || ''} disabled={!editable}
                onChange={e => upd({ projectId: e.target.value || null })}>
                <option value="">— not allocated —</option>
                {(ctx.projects || []).map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
              </Select>
            </Field>
            <Field label="PO / reference">
              <TextInput value={pkg.poNumber} disabled={!editable} onChange={e => upd({ poNumber: e.target.value })} />
            </Field>
          </div>
          <Field label="Notes">
            <TextArea value={pkg.notes} disabled={!editable} onChange={e => upd({ notes: e.target.value })} />
          </Field>
          <div>
            <div className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--leon-black)]/55 mb-1">
              Movements
            </div>
            {!(pkg.movements || []).length ? (
              <p className="text-xs text-[var(--leon-black)]/45">Nothing logged yet.</p>
            ) : (
              <div className="space-y-1 max-h-40 overflow-y-auto">
                {(pkg.movements || []).map(mv => (
                  <div key={mv.id} className="text-[11px] border-b border-[var(--leon-line)]/60 py-1">
                    <span className="font-semibold">{mv.status || 'Moved'}</span>
                    {mv.to ? <> → {mv.to}</> : null}
                    <span className="text-[var(--leon-black)]/40"> · {mv.date} · {mv.by}</span>
                    {mv.note ? <div className="text-[var(--leon-black)]/50">{mv.note}</div> : null}
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>
    </Modal>
  );
}

function LogPackages({ ctx, editable }) {
  const [q, setQ] = useState('');
  const [status, setStatus] = useState('');
  const [openId, setOpenId] = useState(null);
  const whs = logWarehouses(ctx);
  const pkgs = logPackages(ctx);
  const needle = q.trim().toLowerCase();
  const hits = pkgs.filter(p => (!status || p.status === status)
    && (!needle || [p.code, p.name, p.poNumber, p.bin, p.rack, p.zone]
      .some(v => String(v || '').toLowerCase().includes(needle))));
  const open = pkgs.find(p => p.id === openId) || null;

  function add() {
    const code = logNextCode(ctx);
    const p = makeWarehousePackage({ code, warehouseId: whs[0] ? whs[0].id : null }, ctx.currentUserName);
    logSet(ctx, l => [p, ...l]);
    setOpenId(p.id);
  }

  return (
    <div className="space-y-3">
      <div className="flex items-start gap-3 flex-wrap">
        <div className="flex-1 min-w-[260px]">
          <h3 className="font-bold">📦 Packages</h3>
          <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
            The physical things on the rack. Each carries a code that goes on its printed label and into
            its QR, so a scan finds it and a picker can read it across an aisle.
          </p>
        </div>
        {editable && <Button size="sm" onClick={add}>+ New package</Button>}
      </div>
      <div className="flex items-end gap-3 flex-wrap">
        <Field label="Status">
          <Select className="!w-44" value={status} onChange={e => setStatus(e.target.value)}>
            <option value="">Every status</option>
            {WAREHOUSE_PACKAGE_STATUSES.map(x => <option key={x}>{x}</option>)}
          </Select>
        </Field>
        <Field label="Search" className="flex-1">
          <TextInput value={q} onChange={e => setQ(e.target.value)} placeholder="Code, name, PO, bin…" />
        </Field>
        <span className="text-xs text-[var(--leon-black)]/45 pb-2">{hits.length} of {pkgs.length}</span>
      </div>
      {!pkgs.length ? (
        <EmptyState text="No packages yet. A package is a crate, pallet, carton or bundle — the thing a picker actually moves." />
      ) : (
        <div className="overflow-x-auto">
          <table className="w-full text-xs">
            <thead className="text-[10px] uppercase tracking-wide text-[var(--leon-black)]/45">
              <tr className="border-b border-[var(--leon-line)]">
                <th className="text-left py-1 px-2">Code</th><th className="text-left py-1 px-2">What it is</th>
                <th className="text-left py-1 px-2">Status</th><th className="text-left py-1 px-2">Location</th>
                <th className="text-left py-1 px-2">Project</th><th className="text-right py-1 px-2">Qty</th>
              </tr>
            </thead>
            <tbody>
              {hits.map(p => {
                const pj = (ctx.projects || []).find(x => x.id === p.projectId);
                const lost = WAREHOUSE_ON_HAND_STATUSES.indexOf(p.status) >= 0 && !p.bin && !p.rack;
                return (
                  <tr key={p.id} onClick={() => setOpenId(p.id)}
                    className="border-b border-[var(--leon-line)]/60 cursor-pointer hover:bg-[var(--leon-cream)]/50">
                    <td className="py-1 px-2 font-mono font-semibold">{p.code}</td>
                    <td className="py-1 px-2">{p.name || p.kind}</td>
                    <td className="py-1 px-2">{p.status}</td>
                    <td className={`py-1 px-2 ${lost ? 'text-[var(--leon-red)]' : ''}`}>
                      {warehousePackageLocation(p, whs)}
                    </td>
                    <td className="py-1 px-2 text-[var(--leon-black)]/55">{pj ? pj.name : '—'}</td>
                    <td className="py-1 px-2 text-right tabular-nums">{p.qty}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
      {open && <LogPackageEditor ctx={ctx} pkg={open} editable={editable} onClose={() => setOpenId(null)} />}
    </div>
  );
}

// ── Scan / look up ─────────────────────────────────────────────────────────
// A phone camera reads the QR and opens the Hub at ?pkg=CODE. There is no
// camera API in use here — a warehouse scanner is a keyboard that types the
// code and presses Enter, which this field is built for, and the deep link
// covers the phone.
function LogScan({ ctx, editable }) {
  const [code, setCode] = useState('');
  const [openId, setOpenId] = useState(null);
  const pkgs = logPackages(ctx);
  const whs = logWarehouses(ctx);
  const hit = pkgs.find(p => String(p.code).toLowerCase() === code.trim().toLowerCase()) || null;
  const open = pkgs.find(p => p.id === openId) || null;
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">🔍 Scan or look up</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          Scan a label or type its code. A handheld scanner types the code and presses Enter, which is
          what this field expects; a phone reading the QR opens the Hub straight at the package.
        </p>
      </div>
      <div className="max-w-md">
        <Field label="Package code">
          <TextInput autoFocus value={code} onChange={e => setCode(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && hit) setOpenId(hit.id); }}
            placeholder="LP26-0001" />
        </Field>
      </div>
      {code.trim() && !hit && (
        <div className="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
          Nothing here with the code <b>{code.trim()}</b>. Check the label, or the package may not have
          been created yet.
        </div>
      )}
      {hit && (
        <div className="rounded-lg border border-[var(--leon-brown)] bg-[var(--leon-cream)]/60 p-3 max-w-2xl">
          <div className="flex items-baseline gap-2">
            <span className="font-mono font-bold">{hit.code}</span>
            <span className="text-sm">{hit.name || hit.kind}</span>
            <span className="ml-auto text-xs">{hit.status}</span>
          </div>
          <div className="text-xs text-[var(--leon-black)]/60 mt-1">
            {warehousePackageLocation(hit, whs)}
          </div>
          <Button size="sm" className="mt-2" onClick={() => setOpenId(hit.id)}>Open it</Button>
        </div>
      )}
      {open && <LogPackageEditor ctx={ctx} pkg={open} editable={editable} onClose={() => setOpenId(null)} />}
    </div>
  );
}

// ── Labels ─────────────────────────────────────────────────────────────────
// What actually goes on the crate. Printed through the page like everything
// else here, at a real label size, with the QR big enough to scan from a
// forklift and the code big enough to read without one.
function LogLabels({ ctx }) {
  const [picked, setPicked] = useState([]);
  const [size, setSize] = useState('4x6');
  // The QR's centre mark has to be a DATA URI: an SVG used as an <img> src
  // cannot fetch anything, so a plain path renders a blank hole. Loaded once
  // here; the first paint has no logo and the second does, a frame later.
  const [markUri, setMarkUri] = useState(
    typeof LeonQR !== 'undefined' && LeonQR ? LeonQR.logoDataUri('logo/leon-wordmark.svg') : null);
  useEffect(() => {
    if (markUri || typeof LeonQR === 'undefined' || !LeonQR) return;
    let live = true;
    LeonQR.loadLogo('logo/leon-wordmark.svg').then(v => { if (live && v) setMarkUri(v); });
    return () => { live = false; };
  }, [markUri]);
  const pkgs = logPackages(ctx);
  const whs = logWarehouses(ctx);
  const ref = useRef(null);
  const SIZES = { '4x6': { w: 101.6, h: 152.4, label: '4 × 6 in' },
                  '4x3': { w: 101.6, h: 76.2, label: '4 × 3 in' },
                  'a6':  { w: 105, h: 148, label: 'A6' } };
  const S = SIZES[size];
  const chosen = pkgs.filter(p => picked.indexOf(p.id) >= 0);
  const toggle = id => setPicked(prev => prev.indexOf(id) >= 0 ? prev.filter(x => x !== id) : [...prev, id]);
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">🏷️ Print labels</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          One label per package. Print at the label size chosen, with scaling off &mdash; a QR that has been
          scaled by the print dialog may still scan, and may not.
        </p>
      </div>
      <div className="flex items-end gap-3 flex-wrap">
        <Field label="Label size">
          <Select className="!w-40" value={size} onChange={e => setSize(e.target.value)}>
            {Object.keys(SIZES).map(k => <option key={k} value={k}>{SIZES[k].label}</option>)}
          </Select>
        </Field>
        <Button size="sm" variant="outline" onClick={() => setPicked(pkgs.map(p => p.id))}>All</Button>
        <Button size="sm" variant="outline" onClick={() => setPicked([])}>None</Button>
        <span className="text-xs text-[var(--leon-black)]/45 pb-2">{chosen.length} selected</span>
        <div className="ml-auto">
          <PrintButton heading="Package labels" lines={[]} />
        </div>
      </div>
      <div className="flex flex-wrap gap-1.5">
        {pkgs.map(p => (
          <button key={p.id} onClick={() => toggle(p.id)}
            className={`px-2 py-1 rounded border text-[11px] font-mono ${picked.indexOf(p.id) >= 0
              ? 'bg-[var(--leon-brown)] text-white border-[var(--leon-brown)]'
              : 'border-[var(--leon-line)] hover:border-[var(--leon-brown)]'}`}>
            {p.code}
          </button>
        ))}
        {!pkgs.length && <EmptyState text="No packages to label yet." />}
      </div>
      <div ref={ref} data-print-region="Package labels" className="flex flex-wrap gap-3">
        {chosen.map(p => {
          // The QR carries the LEON mark in its middle, so the encoder is
          // forced to level H — the logo is DAMAGE to the code, and level M's
          // 15% recovery leaves nothing spare once a label is also smudged,
          // curled or printed on a cheap thermal roll.
          // The WORDMARK, not the full lockup. The lockup is the lion above
          // LEON — 1:2.24 portrait — and in the middle of a QR it is a sliver
          // nobody recognises. The wordmark is 3.7:1, so with a hole cut to
          // that shape the LEON lettering fills it edge to edge and reads at
          // label size, which is the whole point of putting it there.
          const qr = typeof LeonQR !== 'undefined' && LeonQR
            ? LeonQR.toDataUrl(warehousePackageQr(p), Object.assign(
                { size: 460 },
                markUri ? { logo: markUri, logoAspect: 3.7, logoScale: 0.34 } : {}))
            : '';
          const pj = (ctx.projects || []).find(x => x.id === p.projectId);
          const sc = pj && p.scopeId ? (pj.scopes || []).find(x => x.id === p.scopeId) : null;
          const addr = pj ? (pj.address || '') : '';
          // A label is read from across a warehouse, so the type scale is the
          // design: the code and the job are what somebody is looking for, and
          // everything else supports them. Sized in mm against the real label,
          // not in px, so 4x6 and 4x3 keep the same hierarchy.
          const big = size === '4x3' ? 8 : 12;     // the package code, full width
          const jobPt = size === '4x3' ? 4 : 5.4;  // the project name
          return (
            <div key={p.id} className="bg-white border-2 border-black flex flex-col overflow-hidden"
              style={{ width: `${S.w}mm`, height: `${S.h}mm`, padding: '3mm' }}>

              {/* THE LOCKUP, RELAID FOR A LANDSCAPE LABEL. `leon-official.svg`
                  is the lion ABOVE the wordmark — 1:2.24 portrait — so sizing
                  it to a label's header height leaves a 7 mm sliver nobody
                  reads as a logo. The same artwork is used, arranged across
                  instead of down: the lion at header height, the LEON wordmark
                  beside it at the width the label actually has. Both are the
                  real vector files, never type — no font reproduces the
                  three-bar E or the lion. */}
              <div className="flex items-center" style={{ gap: '2.5mm' }}>
                <img src="logo/leon-mark.svg" alt=""
                  style={{ height: size === '4x3' ? '10mm' : '14mm', width: 'auto', flex: '0 0 auto' }} />
                <img src="logo/leon-wordmark.svg" alt="LEON"
                  style={{ height: size === '4x3' ? '5mm' : '7mm', width: 'auto', flex: '0 0 auto' }} />
                <div className="text-right leading-tight ml-auto">
                  <div style={{ fontSize: '2.8mm', letterSpacing: '0.06em' }} className="font-bold">
                    {String(p.kind || '').toUpperCase()}
                  </div>
                  <div style={{ fontSize: '2.4mm' }} className="opacity-60">{p.status}</div>
                </div>
              </div>

              {/* The job, large. If this package belongs to one, it is the
                  thing being looked for on the rack. */}
              {pj ? (
                <div style={{ marginTop: '1.5mm' }} className="leading-none">
                  <div style={{ fontSize: `${jobPt}mm` }} className="font-bold leading-tight">
                    {pj.name}
                  </div>
                  {addr && (
                    <div style={{ fontSize: '2.6mm', marginTop: '0.8mm' }} className="opacity-75 leading-tight">
                      {addr}
                    </div>
                  )}
                  {sc && (
                    <div style={{ fontSize: '3mm', marginTop: '1mm' }} className="font-semibold">
                      {sc.name}
                    </div>
                  )}
                </div>
              ) : (
                <div style={{ marginTop: '1.5mm', fontSize: `${jobPt}mm` }} className="font-bold opacity-45">
                  Not on a job
                </div>
              )}

              {/* QR and code side by side — the scanner wants one, a person
                  reading the rack wants the other, and stacking them wasted
                  the width. */}
              {/* THE CODE GETS THE FULL WIDTH. Beside the QR it had about
                  45 mm, and a 9-character code at a size worth reading does
                  not fit in that — it wrapped to "LP26-004 / 2", which is the
                  one thing on the label that must never be ambiguous. It is
                  the line somebody reads off a rack from two metres away, so
                  it gets the whole label and nothing competes with it. */}
              <div className="font-mono font-bold leading-none text-center whitespace-nowrap"
                style={{ fontSize: `${big}mm`, marginTop: '2.5mm', letterSpacing: '-0.01em' }}>
                {p.code}
              </div>
              {p.name && (
                <div style={{ fontSize: '3mm', marginTop: '0.8mm' }}
                  className="font-semibold leading-tight text-center truncate">{p.name}</div>
              )}

              {/* The scanning target, as large as the label will give it. */}
              <div className="flex items-center justify-center" style={{ marginTop: '2mm' }}>
                {qr && <img src={qr} alt=""
                  style={{ width: size === '4x3' ? '28mm' : '46mm', height: 'auto' }} />}
              </div>

              {/* Where it is, and what it is against — the facts a picker acts
                  on, ruled off so they read as a block. */}
              <div className="border-t-2 border-black leading-tight"
                style={{ marginTop: 'auto', paddingTop: '1.5mm', fontSize: '2.8mm' }}>
                <div className="flex justify-between" style={{ gap: '2mm' }}>
                  <span className="truncate"><b>Location</b> {warehousePackageLocation(p, whs)}</span>
                  <span className="whitespace-nowrap"><b>Qty</b> {p.qty}</span>
                </div>
                {(p.poNumber || p.containerId) && (
                  <div className="flex justify-between" style={{ gap: '2mm', marginTop: '0.6mm' }}>
                    {p.poNumber && <span className="truncate"><b>PO</b> {p.poNumber}</span>}
                    {p.weightKg ? <span className="whitespace-nowrap"><b>Wt</b> {p.weightKg} kg</span> : null}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Receiving, picking, movements, stock, bins ─────────────────────────────
function LogMoveList({ ctx, editable, statuses, title, blurb, nextStatus, actionLabel }) {
  const whs = logWarehouses(ctx);
  const pkgs = logPackages(ctx).filter(p => statuses.indexOf(p.status) >= 0);
  function advance(p) {
    const from = warehousePackageLocation(p, whs);
    logSet(ctx, l => l.map(x => x.id === p.id ? {
      ...x, status: nextStatus,
      receivedDate: nextStatus === 'Received' ? todayISO() : x.receivedDate,
      movements: [makeWarehouseMovement({ from, to: from, status: nextStatus,
        by: ctx.currentUserName, note: actionLabel }), ...(x.movements || [])],
    } : x));
  }
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">{title}</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">{blurb}</p>
      </div>
      {!pkgs.length ? <EmptyState text="Nothing waiting." /> : (
        <div className="space-y-1.5">
          {pkgs.map(p => (
            <div key={p.id} className="flex items-center gap-3 rounded border border-[var(--leon-line)] bg-white px-3 py-2">
              <span className="font-mono font-semibold text-sm">{p.code}</span>
              <span className="text-sm">{p.name || p.kind}</span>
              <span className="text-xs text-[var(--leon-black)]/50">{warehousePackageLocation(p, whs)}</span>
              <span className="ml-auto text-xs">{p.status}</span>
              {editable && <Button size="sm" onClick={() => advance(p)}>{actionLabel}</Button>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
function LogReceiving({ ctx, editable }) {
  return <LogMoveList ctx={ctx} editable={editable} statuses={['Expected']} nextStatus="Received"
    actionLabel="Receive" title="📥 Receiving"
    blurb="What is expected but not yet in. Receiving a package stamps the date and starts its movement log." />;
}
function LogPicking({ ctx, editable }) {
  return (
    <div className="space-y-5">
      <LogMoveList ctx={ctx} editable={editable} statuses={['In Stock', 'Received', 'Allocated']}
        nextStatus="Picked" actionLabel="Pick" title="📤 To pick"
        blurb="On the rack and wanted. Picking it takes it off the shelf and into the staging area." />
      <LogMoveList ctx={ctx} editable={editable} statuses={['Picked']} nextStatus="Staged"
        actionLabel="Stage" title="Staged to load" blurb="Picked and waiting by the door." />
      <LogMoveList ctx={ctx} editable={editable} statuses={['Staged']} nextStatus="Loaded"
        actionLabel="Load" title="Loading" blurb="On the truck." />
    </div>
  );
}
function LogMovements({ ctx }) {
  const whs = logWarehouses(ctx);
  const rows = [];
  logPackages(ctx).forEach(p => (p.movements || []).forEach(mv => rows.push({ p, mv })));
  rows.sort((a, b) => String(b.mv.at).localeCompare(String(a.mv.at)));
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">🧾 Movement log</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          Every move of every package, newest first. This is the record a warehouse argument is settled
          from, which is the only reason to keep one.
        </p>
      </div>
      {!rows.length ? <EmptyState text="Nothing has moved yet." /> : (
        <div className="overflow-x-auto">
          <table className="w-full text-xs">
            <thead className="text-[10px] uppercase tracking-wide text-[var(--leon-black)]/45">
              <tr className="border-b border-[var(--leon-line)]">
                <th className="text-left py-1 px-2">When</th><th className="text-left py-1 px-2">Package</th>
                <th className="text-left py-1 px-2">What happened</th><th className="text-left py-1 px-2">Where</th>
                <th className="text-left py-1 px-2">By</th>
              </tr>
            </thead>
            <tbody>
              {rows.slice(0, 300).map(({ p, mv }) => (
                <tr key={mv.id} className="border-b border-[var(--leon-line)]/60">
                  <td className="py-1 px-2 whitespace-nowrap">{mv.date}</td>
                  <td className="py-1 px-2 font-mono">{p.code}</td>
                  <td className="py-1 px-2">{mv.status}{mv.note ? ` — ${mv.note}` : ''}</td>
                  <td className="py-1 px-2 text-[var(--leon-black)]/55">{mv.to || mv.from}</td>
                  <td className="py-1 px-2 text-[var(--leon-black)]/55">{mv.by}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}
function LogStock({ ctx }) {
  const mats = ctx.warehouseMaterials || [];
  const [q, setQ] = useState('');
  const needle = q.trim().toLowerCase();
  const hits = mats.filter(m => !needle || [m.name, m.category, m.itemId].some(v => String(v || '').toLowerCase().includes(needle)));
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">🗄️ Stock on hand</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          The Hub's own stock records &mdash; the same ones the Inventory hub keeps. Shown here so a picker
          does not have to leave the warehouse screens to answer "how many are left".
        </p>
      </div>
      <div className="max-w-md"><TextInput value={q} onChange={e => setQ(e.target.value)} placeholder="Search stock…" /></div>
      <div className="overflow-x-auto">
        <table className="w-full text-xs">
          <thead className="text-[10px] uppercase tracking-wide text-[var(--leon-black)]/45">
            <tr className="border-b border-[var(--leon-line)]">
              <th className="text-left py-1 px-2">Item</th><th className="text-left py-1 px-2">Category</th>
              <th className="text-right py-1 px-2">On hand</th><th className="text-left py-1 px-2">Unit</th>
            </tr>
          </thead>
          <tbody>
            {hits.slice(0, 400).map(m => (
              <tr key={m.id} className="border-b border-[var(--leon-line)]/60">
                <td className="py-1 px-2">{m.name}</td>
                <td className="py-1 px-2 text-[var(--leon-black)]/55">{m.category}</td>
                <td className="py-1 px-2 text-right tabular-nums">{m.currentStock}</td>
                <td className="py-1 px-2 text-[var(--leon-black)]/55">{m.unitOfMeasure}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <p className="text-[11px] text-[var(--leon-black)]/45">
        {hits.length} of {mats.length} items. Adding, adjusting and receiving stock is done in
        Logistics &rarr; Inventory, which is where the transaction history lives.
      </p>
    </div>
  );
}
function LogBins({ ctx }) {
  const whs = logWarehouses(ctx);
  const pkgs = logPackages(ctx);
  const map = {};
  pkgs.forEach(p => {
    const k = warehousePackageLocation(p, whs);
    (map[k] = map[k] || []).push(p);
  });
  const keys = Object.keys(map).sort();
  return (
    <div className="space-y-3">
      <div>
        <h3 className="font-bold">🧭 Bins &amp; locations</h3>
        <p className="text-sm text-[var(--leon-black)]/60 max-w-3xl">
          Every location that has something in it, and what is there. Locations are typed as they are
          used rather than defined up front &mdash; a warehouse that has to be modelled before anything can
          be put on a shelf never gets used.
        </p>
      </div>
      <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
        {keys.map(k => (
          <div key={k} className="rounded-lg border border-[var(--leon-line)] bg-white p-2.5">
            <div className={`text-sm font-semibold ${k === 'No location set' ? 'text-[var(--leon-red)]' : ''}`}>{k}</div>
            <div className="text-[11px] text-[var(--leon-black)]/45 mb-1">{map[k].length} package{map[k].length === 1 ? '' : 's'}</div>
            <div className="flex flex-wrap gap-1">
              {map[k].map(p => (
                <span key={p.id} className="px-1.5 py-0.5 rounded border border-[var(--leon-line)] text-[10px] font-mono">
                  {p.code}
                </span>
              ))}
            </div>
          </div>
        ))}
        {!keys.length && <EmptyState text="Nothing is in a location yet." />}
      </div>
    </div>
  );
}
