// ===== Durchlaufplanung: systemübergreifendes Leitstand-Gantt =====
// y-Achse = Arbeitssysteme, x-Achse = Zeit. Pro System die Arbeitsvorgänge der Aufträge,
// inkl. Rüstzeiten (vor dem Vorgang) und Übergangszeiten (nach dem Vorgang, bis zum nächsten System).

// Durchlaufplanung = Abbild der Feinplanungs-Belegung je System, in Routing-Reihenfolge verkettet.
// Ein Arbeitsvorgang an einem System kann nicht starten, bevor der vorgelagerte Schritt desselben
// Auftrags + Übergangszeit fertig ist → keine parallelen Vorgänge eines Auftrags.
const STAGE_RANK = { lager: 0, saegen: 1, drehen: 2, fraesen: 3, waerme: 4, entgraten: 5, pruefen: 6 };

function computeDurchlauf({ planBySystem, lockedBySystem, splittableBySystem, capacityBySystem, transitionBySystem, gapFill, frozenShifts }) {
  const WS = window.WORK_SYSTEMS;
  const orders = window.ORDERS;
  const rows = {}; WS.forEach(w => { rows[w.id] = []; });
  const arrivals = {}; WS.forEach(w => { arrivals[w.id] = {}; });
  const readyAt = {}; orders.forEach(o => { readyAt[o.nr] = 0; }); // früheste Verfügbarkeit des Auftrags
  const lastTransH = {}; orders.forEach(o => { lastTransH[o.nr] = 0; }); // Übergangszeit vom Vorgängersystem
  const transH = (id) => transitionBySystem[id] || 0;

  // alle noch nicht erledigten (Auftrag, Schritt) nach Routing-Stufe gruppieren
  const stepsByRank = {};
  orders.forEach(o => o.arbeitsplan.forEach(s => {
    if (s.status === 'erledigt') return;
    const r = STAGE_RANK[s.key] ?? 9;
    (stepsByRank[r] = stepsByRank[r] || []).push({ o, step: s });
  }));

  const savedCap = window.__capDayHours, savedSplit = window.__splittable;
  let total = window.SHIFT_HOURS;

  Object.keys(stepsByRank).map(Number).sort((a, b) => a - b).forEach(rank => {
    const machineGroups = {};
    stepsByRank[rank].forEach(e => {
      if (!e.step.wsId) {
        // externer Schritt (Lager / Härterei): reine Vorlaufzeit
        readyAt[e.o.nr] = (readyAt[e.o.nr] || 0) + e.step.dauerH;
      } else {
        (machineGroups[e.step.wsId] = machineGroups[e.step.wsId] || []).push(e);
      }
    });
    Object.keys(machineGroups).forEach(wsId => {
      window.__capDayHours = window.buildCapDayHours(capacityBySystem[wsId]);
      window.__splittable = new Set(splittableBySystem[wsId] || []);
      const group = machineGroups[wsId];
      const planSeq = planBySystem[wsId] || [];
      const inPlan = new Set(planSeq);
      const lockedS = new Set((lockedBySystem && lockedBySystem[wsId]) || []);
      const tasks = window.TASKS_BY_SYSTEM[wsId] || [];
      const taskByNr = Object.fromEntries(tasks.map(t => [t.nr, t]));
      const groupNrs = new Set(group.map(e => e.o.nr));
      // tatsächlich eingeplante Vorgänge in Plan-Reihenfolge, mit früheste-Start-Zeit aus dem Vorgänger
      const planOps = planSeq.filter(nr => groupNrs.has(nr) && taskByNr[nr])
        .map(nr => ({ ...taskByNr[nr], earliestH: readyAt[nr] || 0, __locked: lockedS.has(nr), __leadTransH: lastTransH[nr] || 0 }));
      const sched = window.computeSchedule(planOps, { gapFill });
      rows[wsId] = sched.blocks.map(b => ({
        nr: b.op.nr, kurz: b.op.kurz, mat: b.op.mat, avo: b.op.avo, order: b.op, wsId,
        setupStart: b.setupStart, opStart: b.opStart, end: b.end, setupMin: b.setupMin,
        segments: b.segments, leadTransH: b.op.__leadTransH || 0, arrivalH: b.op.earliestH || 0,
        locked: b.op.__locked, split: b.split,
      }));
      sched.blocks.forEach((b) => {
        arrivals[wsId][b.op.nr] = b.op.earliestH || 0;
        readyAt[b.op.nr] = b.end + transH(wsId);
        lastTransH[b.op.nr] = transH(wsId);
        total = Math.max(total, b.end + transH(wsId));
      });
      // an diesem System vorhandene, aber (noch) nicht eingeplante Aufträge: Vorlaufzeit schätzen
      group.forEach(e => { if (!inPlan.has(e.o.nr)) readyAt[e.o.nr] = (readyAt[e.o.nr] || 0) + e.step.dauerH + transH(wsId); });
    });
  });

  window.__capDayHours = savedCap; window.__splittable = savedSplit;

  // KPIs über alle Aufträge: mittlere Durchlaufzeit (letzter Vorgang − erster Vorgang) und
  // Termintreue des LETZTEN Arbeitsvorgangs (Fertigstellung ≤ Plan-Ende).
  const span = {};
  Object.values(rows).forEach(list => list.forEach(b => {
    const s = span[b.nr] || { first: Infinity, last: -Infinity };
    s.first = Math.min(s.first, b.opStart);
    s.last = Math.max(s.last, b.end);
    span[b.nr] = s;
  }));
  const nrs = Object.keys(span);
  const orderByNr = Object.fromEntries(orders.map(o => [o.nr, o]));
  const DAY = window.SHIFT_HOURS * 3;
  const endOfDay = (h) => (Math.floor(h / DAY) + 1) * DAY; // termintreu, wenn am Plan-Ende-TAG fertig
  let leadSum = 0, onTime = 0;
  const orderStats = []; // {nr, kurz, kunde, leadH, lastEnd, planEnde, lateH, onTime}
  nrs.forEach(nr => {
    const leadH = span[nr].last - span[nr].first;
    leadSum += leadH;
    const o = orderByNr[nr];
    const planEnde = o ? o.planEnde : Infinity;
    const due = o ? endOfDay(planEnde) : Infinity; // Ende des Plan-Ende-Tages
    const ok = o && span[nr].last <= due + 0.001;
    if (ok) onTime++;
    // Verzug in vollen Tagen (über den Plan-Ende-Tag hinaus)
    const lateDays = o ? Math.max(0, Math.ceil((span[nr].last - due) / DAY)) : 0;
    orderStats.push({ nr, kurz: o ? o.kurz : nr, kunde: o ? o.kunde : '', mat: o ? o.mat : null,
      leadH, lastEnd: span[nr].last, planEnde, lateH: Math.max(0, span[nr].last - planEnde), lateDays, onTime: ok });
  });
  const kpi = { meanLeadH: nrs.length ? leadSum / nrs.length : 0, onTime, total: nrs.length };

  // ── Widget-Kennzahlen je Arbeitssystem ──
  const netDay = (cap) => { const n = Math.max(0, ((cap.hoursPerShift || 8) * 60 - cap.breaks) / 60); return cap.systems * cap.shiftsPerDay * n * cap.oee / 100; };
  const fzShifts = frozenShifts || 8;
  const systemStats = window.WORK_SYSTEMS.map(w => {
    const list = rows[w.id] || [];
    const cap = (capacityBySystem && capacityBySystem[w.id]) || window.defaultCapacity();
    const openS = window.buildOpenShifts(cap);
    // Frozen-Zone-Ende dieses Systems: N *offene* Schichten in die Zukunft (geschlossene Schichten überspringen)
    let count = 0, fzEndH = fzShifts * window.SHIFT_HOURS;
    for (let i = 0; i < 3000; i++) {
      if ((i % 3) < openS(Math.floor(i / 3))) { count++; if (count >= fzShifts) { fzEndH = (i + 1) * window.SHIFT_HOURS; break; } }
    }
    // verfügbare EFFEKTIVE Arbeitskapazität (Netto × OEE × Systeme) innerhalb der Frozen Zone
    const capEff = window.buildCapDayHours(cap);
    let availH = 0;
    for (let d = 0; d * 24 < fzEndH; d++) availH += capEff(d);
    // belegte Stunden (Rüsten + Bearbeitung), auf das Frozen-Zone-Fenster [0, fzEndH] geklippt
    const clip = (s, e) => Math.max(0, Math.min(e, fzEndH) - Math.max(s, 0));
    const busyH = list.reduce((a, b) => a + clip(b.setupStart, b.end), 0);
    const util = availH > 0 ? Math.min(1, busyH / availH) : 0;
    const nd = netDay(cap) || 1;
    const backlogH = (window.TASKS_BY_SYSTEM[w.id] || []).filter(t => !t.released).reduce((a, t) => a + t.dauerH, 0);
    const reachDays = backlogH / nd;
    return { id: w.id, name: w.name, util, busyH, availH, fzEndH, backlogH, reachDays, ops: list.length };
  });

  return { rows, total, arrivals, kpi, orderStats, systemStats };
}

function DurchlaufBoard({ data, selected, onSelect, shiftPx: shiftPx0 = 64, capacityBySystem, splitBySystem, onReorder, onToggleSplit }) {
  const WS = window.WORK_SYSTEMS;
  const [shiftPx, setShiftPx] = React.useState(shiftPx0);
  // Weiter Zeitbereich: Standard ~1 Woche sichtbar, scrollbar weit in Vergangenheit & Zukunft.
  const pastDays = 180;
  const futureDays = Math.max(180, Math.ceil(data.total / 24) + 21);
  const startDay = -pastDays, totalDays = pastDays + futureDays;
  const startH = startDay * 24;
  const px = (h) => ((h - startH) / window.SHIFT_HOURS) * shiftPx;
  const pxw = (h) => (h / window.SHIFT_HOURS) * shiftPx; // Breite ohne Ursprungs-Offset
  const W = totalDays * 3 * shiftPx;
  const LABEL_W = 188, ROW_H = 46;
  const gridBg = `repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px ${shiftPx}px), repeating-linear-gradient(90deg, var(--line-2) 0 1px, transparent 1px ${shiftPx * 3}px)`;
  const winFrom = -14, winTo = Math.ceil(data.total / 24) + 21; // Fenster für geschlossene Schichten
  // initial auf „heute" scrollen
  const scrollRef = React.useRef(null);
  const didScroll = React.useRef(false);
  React.useEffect(() => {
    if (scrollRef.current && !didScroll.current) { scrollRef.current.scrollLeft = px(0) - shiftPx * 3; didScroll.current = true; }
  });
  // Zoom per Mausrad (Cursor-verankert)
  const onWheel = (e) => {
    if (e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
    e.preventDefault();
    const el = scrollRef.current; if (!el) return;
    const rect = el.getBoundingClientRect();
    const cursorContentX = e.clientX - rect.left + el.scrollLeft - LABEL_W;
    const factor = e.deltaY < 0 ? 1.18 : 1 / 1.18;
    setShiftPx(prev => {
      const next = Math.max(20, Math.min(600, prev * factor));
      const ratio = next / prev;
      requestAnimationFrame(() => { if (el) el.scrollLeft = (cursorContentX * ratio) + LABEL_W - (e.clientX - rect.left); });
      return next;
    });
  };
  const hourPx = shiftPx / window.SHIFT_HOURS;
  const showTimes = shiftPx >= 110;
  const shiftStart = (i) => ['06:00', '14:00', '22:00'][((i % 3) + 3) % 3];

  const renderRow = (list, wsId) => {
    const splitSet = new Set((splitBySystem && splitBySystem[wsId]) || []);
    const out = [];
    list.forEach((b, i) => {
      const m = window.MATERIALS[b.mat];
      const sel = selected === b.nr;
      const segs = b.segments && b.segments.length ? b.segments : [{ start: b.opStart, end: b.end }];
      const left = px(b.setupStart), prevEnd = i > 0 ? list[i - 1].end : 0;
      // Übergangszeit = Transfer vom Vorgängersystem, im ECHTEN Transit-Fenster [Ankunft − Übergang, Ankunft].
      // Nur zeichnen, soweit der Transfer NACH dem vorherigen Vorgang liegt (sonst war der Auftrag nur am Warten).
      if (b.leadTransH > 0) {
        const transitStart = Math.max(b.arrivalH - b.leadTransH, prevEnd);
        const transitEnd = b.arrivalH;
        if (transitEnd - transitStart > 0.1) {
          out.push(<div key={b.nr + '-trans'} title={`Übergangszeit ${b.leadTransH} h — Transfer vom Vorgängersystem`}
            style={{ position: 'absolute', left: px(transitStart), width: pxw(transitEnd - transitStart), top: 9, bottom: 9,
              background: 'repeating-linear-gradient(90deg, color-mix(in oklch, var(--accent-ink) 32%, transparent) 0 2px, transparent 2px 5px)', borderRadius: 2 }} />);
        }
      }
      // Rüstzeit
      if (b.setupMin >= 12) {
        out.push(<div key={b.nr + '-setup'} title={`Rüsten ${b.setupMin} min`}
          style={{ position: 'absolute', left: px(b.setupStart), width: Math.max(pxw(b.opStart - b.setupStart), 2), top: 4, bottom: 4,
            background: 'repeating-linear-gradient(45deg, var(--line-2) 0 3px, transparent 3px 6px)', opacity: .85, borderRadius: 2 }} />);
      }
      // Vorgang (ggf. gesplittet)
      const canSplit = splitSet.has(b.nr);
      segs.forEach((seg, si) => {
        const sl = px(seg.start), sw = Math.max(pxw(seg.end - seg.start), 14);
        const first = si === 0, last = si === segs.length - 1;
        out.push(
          <div key={b.nr + '-' + si} onClick={() => onSelect && onSelect(sel ? null : b.nr, b.wsId)}
            draggable
            onDragStart={e => { e.dataTransfer.setData('text/plain', JSON.stringify({ nr: b.nr, wsId: b.wsId })); e.dataTransfer.effectAllowed = 'move'; }}
            onDragOver={e => { if (e.dataTransfer.types.includes('text/plain')) e.preventDefault(); }}
            onDrop={e => { e.preventDefault(); e.stopPropagation(); try { const p = JSON.parse(e.dataTransfer.getData('text/plain')); if (p.wsId === b.wsId && p.nr !== b.nr) onReorder && onReorder(b.wsId, p.nr, b.nr); } catch (err) {} }}
            onDoubleClick={e => { e.stopPropagation(); onToggleSplit && onToggleSplit(b.wsId, b.nr); }}
            title={`${b.nr} · ${b.kurz}\nAVO ${b.avo} · ${b.mat} · ${(b.end - b.opStart).toFixed(1)} h${b.setupMin >= 12 ? ` · Rüsten ${b.setupMin}′` : ''}${b.leadTransH ? ` · Übergang davor ${b.leadTransH} h` : ''}${canSplit ? ' · splitten erlaubt' : ''}\nPlan-Ende: ${b.order ? window.fmtDate(b.order.planEnde, true) : '—'} · fertig: ${window.fmtDate(b.end, true)}\nZiehen zum Umsortieren · Doppelklick = Splitten ein/aus`}
            style={{ position: 'absolute', left: sl, width: sw, top: 4, bottom: 4, cursor: 'grab',
              background: sel ? 'var(--accent)' : `color-mix(in oklch, ${m.color} 28%, var(--surface))`,
              border: `1px solid ${sel ? 'var(--accent-ink)' : `color-mix(in oklch, ${m.color} 52%, var(--line))`}`,
              borderLeft: first ? `3px solid ${sel ? 'var(--accent-ink)' : m.color}` : `1px dashed color-mix(in oklch, ${m.color} 60%, var(--line))`,
              borderTopLeftRadius: first ? 4 : 0, borderBottomLeftRadius: first ? 4 : 0,
              borderTopRightRadius: last ? 4 : 0, borderBottomRightRadius: last ? 4 : 0,
              boxShadow: sel ? '0 0 0 2px var(--accent-ink)' : 'none', zIndex: sel ? 3 : 1,
              display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
            {first && <span className="num" style={{ display: 'flex', alignItems: 'center', gap: 3, fontFamily: 'var(--mono)', fontSize: 10, fontWeight: 600, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {b.locked && <Icon name="lock" size={9} style={{ color: 'var(--accent-ink)', flex: '0 0 auto' }} />}
              {canSplit && <Icon name="scissors" size={9} style={{ color: 'var(--ink-2)', flex: '0 0 auto' }} />}
              {b.nr.replace('FA-', '')}{segs.length > 1 && <span style={{ color: 'var(--muted)', fontWeight: 400 }}>·{si + 1}</span>}
            </span>}
            {first && sw > 60 && <span style={{ fontSize: 9.5, color: 'var(--ink-2)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{b.kurz}</span>}
          </div>
        );
      });
    });
    return out;
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden', boxShadow: 'var(--shadow)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--line)', flex: '0 0 auto' }}>
        <Icon name="layers" size={15} style={{ color: 'var(--ink-2)' }} />
        <span style={{ fontWeight: 600, fontSize: 13.5 }}>Durchlaufplanung</span>
        <span className="label">Arbeitssysteme · Belegung über alle Stufen</span>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 20 }}>
          {data.kpi && data.kpi.total > 0 && (
            <div style={{ display: 'flex', gap: 18, alignItems: 'center', paddingRight: 18, borderRight: '1px solid var(--line)' }}>
              <div style={{ textAlign: 'right' }}>
                <div className="num" style={{ fontFamily: 'var(--mono)', fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{(data.kpi.meanLeadH / (window.SHIFT_HOURS * 3)).toFixed(1).replace('.', ',')} Tage</div>
                <div className="label" style={{ fontSize: 9 }}>Ø Durchlaufzeit</div>
              </div>
              <div style={{ textAlign: 'right' }}>
                <div className="num" style={{ fontFamily: 'var(--mono)', fontSize: 14, fontWeight: 600, color: data.kpi.onTime === data.kpi.total ? 'var(--good-ink)' : 'var(--warn-ink)' }}>{data.kpi.onTime}/{data.kpi.total}</div>
                <div className="label" style={{ fontSize: 9 }}>Termintreue</div>
              </div>
            </div>
          )}
          <div style={{ display: 'flex', gap: 14 }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10.5, color: 'var(--muted)' }}>
            <span style={{ width: 16, height: 9, background: 'repeating-linear-gradient(45deg, var(--line-2) 0 3px, transparent 3px 6px)', border: '1px solid var(--line-2)' }} /> Rüsten
          </span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10.5, color: 'var(--muted)' }}>
            <span style={{ width: 16, height: 9, background: 'repeating-linear-gradient(90deg, color-mix(in oklch, var(--accent-ink) 30%, transparent) 0 2px, transparent 2px 5px)' }} /> Übergang
          </span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 10.5, color: 'var(--muted)' }}>
            <span style={{ width: 16, height: 9, background: 'repeating-linear-gradient(45deg, color-mix(in oklch, var(--bad) 34%, var(--surface-2)) 0 4px, var(--surface-2) 4px 9px)', border: '1px solid var(--line)' }} /> geschlossen
          </span>
          </div>
        </div>
      </div>

      <div ref={scrollRef} onWheel={onWheel} style={{ flex: 1, overflow: 'auto' }}>
        <div style={{ display: 'flex', minWidth: LABEL_W + W }}>
          {/* y-Achse: Arbeitssysteme */}
          <div style={{ width: LABEL_W, flex: '0 0 auto', position: 'sticky', left: 0, zIndex: 4, background: 'var(--panel)', borderRight: '1px solid var(--line-2)' }}>
            <div style={{ height: 40, borderBottom: '1px solid var(--line)' }} />
            {WS.map(w => (
              <div key={w.id} style={{ height: ROW_H, borderBottom: '1px solid var(--line)', padding: '0 12px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
                <span style={{ fontSize: 12.5, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{w.name}</span>
                <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 9.5, color: 'var(--muted)' }}>{w.id} · {(data.rows[w.id] || []).length} Vorg.</span>
              </div>
            ))}
          </div>

          {/* Timeline */}
          <div style={{ width: W, flex: '0 0 auto', position: 'relative' }}>
            {/* Tageskopf mit Datum */}
            <div style={{ height: 40, borderBottom: '1px solid var(--line)', position: 'relative' }}>
              {Array.from({ length: totalDays }, (_, k) => { const d = startDay + k; return (
                <div key={k} style={{ position: 'absolute', left: px(d * 24), width: shiftPx * 3, top: 0, bottom: 0, borderLeft: '1px solid var(--line-2)', paddingLeft: 6, display: 'flex', alignItems: 'center', boxSizing: 'border-box' }}>
                  <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 10.5, fontWeight: 600, color: d < 0 ? 'var(--muted)' : 'var(--ink-2)' }}>{window.fmtDate(d * 24, true)}</span>
                </div>
              ); })}
              {/* „Jetzt"-Linie im Kopf */}
              <div style={{ position: 'absolute', left: px(0), top: 0, bottom: 0, borderLeft: '1.5px solid var(--accent-ink)' }} />
              {/* Schicht-Startzeiten beim Hineinzoomen */}
              {showTimes && Array.from({ length: (winTo - winFrom) * 3 }, (_, j) => {
                const i = winFrom * 3 + j;
                return <span key={'t' + i} className="num" style={{ position: 'absolute', left: px(i * window.SHIFT_HOURS) + 4, bottom: 3, fontFamily: 'var(--mono)', fontSize: 8.5, color: 'var(--muted)' }}>{shiftStart(i)}</span>;
              })}
            </div>
            {/* System-Zeilen */}
            {WS.map(w => {
              const openS = window.buildOpenShifts((capacityBySystem && capacityBySystem[w.id]) || window.defaultCapacity());
              const openShifts = (d) => openS(d);
              return (
                <div key={w.id} style={{ height: ROW_H, borderBottom: '1px solid var(--line)', position: 'relative', backgroundImage: gridBg }}>
                  {/* geschlossene Schichten (nur im Fenster) */}
                  {Array.from({ length: (winTo - winFrom) * 3 }, (_, j) => { const i = winFrom * 3 + j; const open = (((i % 3) + 3) % 3) < openShifts(Math.floor(i / 3)); return open ? null : i; }).filter(i => i !== null).map(i => (
                    <div key={'cl' + i} title="geschlossen — keine Kapazität"
                      style={{ position: 'absolute', left: px(i * window.SHIFT_HOURS), width: shiftPx, top: 0, bottom: 0,
                        background: 'repeating-linear-gradient(45deg, color-mix(in oklch, var(--bad) 30%, var(--surface-2)) 0 4px, var(--surface-2) 4px 9px)' }} />
                  ))}
                  {/* „Jetzt"-Linie */}
                  <div style={{ position: 'absolute', left: px(0), top: 0, bottom: 0, borderLeft: '1.5px solid color-mix(in oklch, var(--accent-ink) 50%, transparent)', pointerEvents: 'none' }} />
                  {renderRow(data.rows[w.id] || [], w.id)}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { computeDurchlauf, DurchlaufBoard, DurchlaufView });

function DurchlaufView({ data, selected, onSelect, onSolveAll, capacityBySystem, splitBySystem, onReorder, onToggleSplit, onUndo, canUndo }) {
  return (
    <React.Fragment>
      <header style={{ flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 14, padding: '0 18px', height: 56, background: 'var(--panel)', borderBottom: '1px solid var(--line-2)' }}>
        <span style={{ fontSize: 14.5, fontWeight: 600 }}>Durchlaufplanung</span>
        <span className="label">Leitstand · alle Arbeitssysteme</span>
        <span style={{ fontSize: 11, color: 'var(--muted)' }}>Ziehen = umsortieren · Doppelklick = splitten</span>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 9 }}>
          <IconBtn icon="undo" label="Undo" onClick={onUndo} disabled={!canUndo} />
          <IconBtn icon="play" label="Alle Systeme berechnen" primary onClick={onSolveAll} />
        </div>
      </header>
      <main style={{ flex: 1, minHeight: 0, padding: 12, display: 'grid', gridTemplateRows: 'minmax(0, 1.35fr) minmax(0, 1fr)', gap: 12 }}>
        <DurchlaufBoard data={data} selected={selected} onSelect={onSelect} capacityBySystem={capacityBySystem} splitBySystem={splitBySystem} onReorder={onReorder} onToggleSplit={onToggleSplit} />
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12, minHeight: 0 }}>
          <LateWidget data={data} selected={selected} onSelect={onSelect} />
          <LeadWidget data={data} selected={selected} onSelect={onSelect} />
          <UtilWidget data={data} />
          <ReachWidget data={data} />
        </div>
      </main>
    </React.Fragment>
  );
}

// ── Widget-Grundgerüst ──
function Widget({ icon, title, sub, accent, sortDir, onToggleSort, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', minHeight: 0, background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden', boxShadow: 'var(--shadow)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '9px 12px', borderBottom: '1px solid var(--line)', flex: '0 0 auto' }}>
        <span style={{ display: 'grid', placeItems: 'center', width: 22, height: 22, borderRadius: 5, background: accent || 'var(--surface-2)', color: 'var(--ink-2)' }}><Icon name={icon} size={13} /></span>
        <span style={{ fontWeight: 600, fontSize: 12.5 }}>{title}</span>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 7 }}>
          {sub && <span className="label" style={{ fontSize: 9 }}>{sub}</span>}
          {onToggleSort && (
            <button onClick={onToggleSort} title={sortDir === 1 ? 'größter Wert oben — klicken für kleinsten' : 'kleinster Wert oben — klicken für größten'}
              style={{ display: 'inline-flex', alignItems: 'center', gap: 3, height: 22, padding: '0 7px', borderRadius: 5, border: '1px solid var(--line-2)', background: 'var(--surface)', color: 'var(--ink-2)', fontFamily: 'var(--mono)', fontSize: 10.5, fontWeight: 600 }}>
              {sortDir === 1 ? '▼ max' : '▲ min'}
            </button>
          )}
        </div>
      </div>
      <div style={{ overflow: 'auto', flex: 1 }}>{children}</div>
    </div>
  );
}

function WRow({ rank, onClick, active, left, mid, right, rightTone }) {
  return (
    <div onClick={onClick} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '7px 12px', borderBottom: '1px solid var(--line)', cursor: onClick ? 'pointer' : 'default',
      background: active ? 'var(--accent)' : 'transparent', boxShadow: active ? 'inset 3px 0 0 var(--accent-ink)' : 'none' }}>
      <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 10.5, fontWeight: 700, color: 'var(--muted)', width: 16, textAlign: 'right' }}>{rank}</span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{left}</div>
        {mid && <div style={{ fontSize: 10.5, color: 'var(--muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{mid}</div>}
      </div>
      <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 12.5, fontWeight: 600, color: rightTone || 'var(--ink)', whiteSpace: 'nowrap', textAlign: 'right' }}>{right}</span>
    </div>
  );
}

function EmptyW({ text }) {
  return <div style={{ padding: '20px 12px', textAlign: 'center', color: 'var(--muted)', fontSize: 11.5 }}>{text}</div>;
}

const { useState: useWState } = React;
// Durchlaufzeit in Arbeitstagen (1 Tag = 3 Schichten = 24 h)
const HOURS_PER_DAY = window.SHIFT_HOURS * 3;
const fmtTage = (h) => (h / HOURS_PER_DAY).toFixed(1).replace('.', ',') + ' T';

// 1) Aufträge, die nicht termintreu sind — alle, sortierbar nach Verzug
function LateWidget({ data, selected, onSelect }) {
  const [dir, setDir] = useWState(1);
  const late = (data.orderStats || []).filter(o => !o.onTime).sort((a, b) => (b.lateH - a.lateH) * dir);
  return (
    <Widget icon="alert" title="Nicht termintreu" sub={`${late.length} Aufträge`} accent="color-mix(in oklch, var(--bad) 45%, var(--surface))"
      sortDir={dir} onToggleSort={() => setDir(d => -d)}>
      {late.length === 0 ? <EmptyW text="Alle Aufträge termintreu ✓" />
        : late.map((o, i) => (
          <WRow key={o.nr} rank={i + 1} onClick={() => onSelect(selected === o.nr ? null : o.nr)} active={selected === o.nr}
            left={<span><span className="num" style={{ fontFamily: 'var(--mono)', fontWeight: 600 }}>{o.nr.replace('FA-', '')}</span> {o.kurz}</span>}
            mid={`Soll ${window.fmtDate(o.planEnde, true)} · Ist ${window.fmtDate(o.lastEnd, true)}`}
            right={'+' + o.lateDays + (o.lateDays === 1 ? ' Tag' : ' Tage')} rightTone="var(--bad-ink)" />
        ))}
    </Widget>
  );
}

// 2) Durchlaufzeit je Auftrag — alle, sortierbar
function LeadWidget({ data, selected, onSelect }) {
  const [dir, setDir] = useWState(1);
  const list = (data.orderStats || []).slice().sort((a, b) => (b.leadH - a.leadH) * dir);
  return (
    <Widget icon="clock" title="Durchlaufzeit" sub={`${list.length} Aufträge`} accent="color-mix(in oklch, var(--warn) 45%, var(--surface))"
      sortDir={dir} onToggleSort={() => setDir(d => -d)}>
      {list.length === 0 ? <EmptyW text="Keine Aufträge eingeplant" />
        : list.map((o, i) => (
          <WRow key={o.nr} rank={i + 1} onClick={() => onSelect(selected === o.nr ? null : o.nr)} active={selected === o.nr}
            left={<span><span className="num" style={{ fontFamily: 'var(--mono)', fontWeight: 600 }}>{o.nr.replace('FA-', '')}</span> {o.kurz}</span>}
            mid={o.kunde}
            right={fmtTage(o.leadH)} rightTone="var(--ink)" />
        ))}
    </Widget>
  );
}

// 3) Auslastung je Arbeitssystem — alle, sortierbar
function UtilWidget({ data }) {
  const [dir, setDir] = useWState(-1); // Standard: niedrigste oben
  const list = (data.systemStats || []).slice().sort((a, b) => (b.util - a.util) * dir);
  return (
    <Widget icon="layers" title="Auslastung" sub={`Ø Frozen Zone · ${list.length} Systeme`} accent="color-mix(in oklch, var(--accent) 50%, var(--surface))"
      sortDir={dir} onToggleSort={() => setDir(d => -d)}>
      {list.length === 0 ? <EmptyW text="Keine Belegung" />
        : list.map((s, i) => (
          <div key={s.id} title={`${Math.round(s.busyH)} h belegt / ${Math.round(s.availH)} h Kapazität in der Frozen Zone`} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '7px 12px', borderBottom: '1px solid var(--line)' }}>
            <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 10.5, fontWeight: 700, color: 'var(--muted)', width: 16, textAlign: 'right' }}>{i + 1}</span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 12, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{s.name}</div>
              <div style={{ height: 5, borderRadius: 99, background: 'var(--line)', marginTop: 4, overflow: 'hidden' }}>
                <div style={{ width: Math.round(s.util * 100) + '%', height: '100%', background: s.util < 0.4 ? 'var(--bad-ink)' : s.util < 0.7 ? 'var(--warn-ink)' : 'var(--good-ink)', borderRadius: 99 }} />
              </div>
            </div>
            <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 12.5, fontWeight: 600, color: 'var(--ink)', width: 38, textAlign: 'right' }}>{Math.round(s.util * 100)}%</span>
          </div>
        ))}
    </Widget>
  );
}

// 4) Reichweite (Auftragsbestand) je Arbeitssystem — alle, sortierbar
function ReachWidget({ data }) {
  const [dir, setDir] = useWState(1); // Standard: höchste oben
  const list = (data.systemStats || []).slice().sort((a, b) => (b.reachDays - a.reachDays) * dir);
  return (
    <Widget icon="box" title="Reichweite" sub={`${list.length} Systeme`} accent="color-mix(in oklch, var(--good) 45%, var(--surface))"
      sortDir={dir} onToggleSort={() => setDir(d => -d)}>
      {list.length === 0 ? <EmptyW text="Kein Auftragsbestand" />
        : list.map((s, i) => (
          <WRow key={s.id} rank={i + 1}
            left={s.name}
            mid={`${Math.round(s.backlogH)} h Bestand`}
            right={s.reachDays.toFixed(1).replace('.', ',') + ' T'} rightTone="var(--ink)" />
        ))}
    </Widget>
  );
}
