// ===== Hauptanwendung =====
const { useState: useS, useMemo, useEffect: useE, useRef: useR } = React;

function App() {
  const SYSTEMS = window.WORK_SYSTEMS;
  const initPerSystem = (fn) => Object.fromEntries(SYSTEMS.map(w => [w.id, fn(w)]));
  const [state, setState] = useS({
    workSystem: SYSTEMS[1] ? SYSTEMS[1].id : SYSTEMS[0].id, // Default: Drehzentrum CTX 4
    frozenShifts: 8,
    weights: { ...window.DEFAULT_WEIGHTS },
    kundeMap: { ...window.DEFAULT_KUNDE_MAP },
    restrictions: [
      { id: 1, from: 'MS58', to: 'TI5', active: true },   // Messing -> Titan (Kontamination)
      { id: 2, from: 'ST42', to: 'AL70', active: false },
    ],
    goalBySystem: initPerSystem(w => ({ ...(w.goal || { durchlauf: 35, ruesten: 75, termin: 50 }) })),
    setupMatrix: window.buildDefaultMatrix(),
    capacityBySystem: initPerSystem(() => window.defaultCapacity()),
    transitionBySystem: (() => { try { const s = JSON.parse(localStorage.getItem('solver.transitions') || 'null'); return s && typeof s === 'object' ? { ...window.TRANSITION_DEFAULT, ...s } : { ...window.TRANSITION_DEFAULT }; } catch (e) { return { ...window.TRANSITION_DEFAULT }; } })(),
    planBySystem: initPerSystem(w => window.TASKS_BY_SYSTEM[w.id].filter(t => t.released).map(t => t.nr)),
    lockedBySystem: initPerSystem(w => window.TASKS_BY_SYSTEM[w.id].filter(t => t.released).map(t => t.nr)),
    splittableBySystem: initPerSystem(() => []),
    mengeOverride: {},
    gapFill: true,
    solving: false,
    selected: null,
  });
  const set = (updater) => setState(s => typeof updater === 'function' ? updater(s) : { ...s, ...updater });
  const [admin, setAdmin] = useS(false);
  const [adminPage, setAdminPage] = useS('einplanung');
  const [dragOver, setDragOver] = useS(false);
  const [page, setPage] = useS('feinplanung');
  const [expanded, setExpanded] = useS(() => new Set());
  const [losOp, setLosOp] = useS(null);
  const toggleExpand = (nr) => setExpanded(s => { const n = new Set(s); n.has(nr) ? n.delete(nr) : n.add(nr); return n; });
  const openAdmin = (pg) => { setAdminPage(pg || 'einplanung'); setAdmin(true); };

  // ── Undo-Historie für Planungsschritte (Reihenfolge, Fixierungen, Splits) ──
  const histRef = useR([]);
  const prevRef = useR(null);
  const undoingRef = useR(false);
  const [histLen, setHistLen] = useS(0);
  useE(() => {
    const snap = { planBySystem: state.planBySystem, lockedBySystem: state.lockedBySystem, splittableBySystem: state.splittableBySystem };
    if (prevRef.current && !undoingRef.current) {
      histRef.current.push(prevRef.current);
      if (histRef.current.length > 100) histRef.current.shift();
      setHistLen(histRef.current.length);
    }
    prevRef.current = snap;
    undoingRef.current = false;
  }, [state.planBySystem, state.lockedBySystem, state.splittableBySystem]);
  const undo = () => {
    if (!histRef.current.length) return;
    const prev = histRef.current.pop();
    undoingRef.current = true;
    setHistLen(histRef.current.length);
    set(s => ({ ...s, planBySystem: prev.planBySystem, lockedBySystem: prev.lockedBySystem, splittableBySystem: prev.splittableBySystem }));
  };

  // Score-Funktion + Rüstzeitmatrix + Kapazität global verfügbar machen (Tabellen/Gantt/Solver lesen sie beim Rendern)
  window.__score = (o) => window.computeScore(o, state.weights, state.kundeMap).total;
  window.__setupMatrix = state.setupMatrix;
  window.__transitionH = state.transitionBySystem[state.workSystem] ?? 0;
  useE(() => { try { localStorage.setItem('solver.transitions', JSON.stringify(state.transitionBySystem)); } catch (e) {} }, [state.transitionBySystem]);
  const capModel = state.capacityBySystem[state.workSystem];
  window.__capDayHours = window.buildCapDayHours(capModel);
  window.__openShifts = window.buildOpenShifts(capModel);

  // Aktives System: Vorgänge, Reihenfolge, Fixierungen, Splits, Ziel
  const sysId = state.workSystem;
  const tasks = window.TASKS_BY_SYSTEM[sysId] || [];
  const plan = state.planBySystem[sysId] || [];
  const locked = state.lockedBySystem[sysId] || [];
  const splittable = state.splittableBySystem[sysId] || [];
  const goal = state.goalBySystem[sysId];

  // Per-System-Slices atomar aktualisieren
  const setPlanState = (fn) => set(s => {
    const id = s.workSystem;
    const cur = { plan: s.planBySystem[id] || [], locked: s.lockedBySystem[id] || [], splittable: s.splittableBySystem[id] || [] };
    const next = fn(cur);
    return {
      ...s,
      planBySystem: { ...s.planBySystem, [id]: next.plan ?? cur.plan },
      lockedBySystem: { ...s.lockedBySystem, [id]: next.locked ?? cur.locked },
      splittableBySystem: { ...s.splittableBySystem, [id]: next.splittable ?? cur.splittable },
    };
  });

  const byNr = useMemo(() => Object.fromEntries(tasks.map(t => [t.nr, t])), [tasks]);
  const lockedSet = useMemo(() => new Set(locked), [locked]);
  const splitSet = useMemo(() => new Set(splittable), [splittable]);
  window.__splittable = splitSet;
  const inPlan = useMemo(() => new Set(plan), [plan]);

  // Durchlaufplanung-Daten zuerst berechnen — liefert die verketteten Ankunftszeiten (Vorgänger + Übergangszeit),
  // die auch die Feinplanung des aktiven Systems verwendet (eine Quelle der Wahrheit → keine parallelen Vorgänge).
  const durchlaufData = useMemo(
    () => window.computeDurchlauf({ planBySystem: state.planBySystem, lockedBySystem: state.lockedBySystem, splittableBySystem: state.splittableBySystem, capacityBySystem: state.capacityBySystem, transitionBySystem: state.transitionBySystem, gapFill: state.gapFill, frozenShifts: state.frozenShifts }),
    [state.planBySystem, state.lockedBySystem, state.splittableBySystem, state.capacityBySystem, state.transitionBySystem, state.setupMatrix, state.gapFill, state.frozenShifts]
  );
  const arrivals = durchlaufData.arrivals[sysId] || {};

  const mengeOf = (o) => state.mengeOverride[o.nr] ?? o.menge;
  const effOp = (o) => { const m = mengeOf(o); const scale = m / Math.max(1, o.menge); return { ...o, menge: m, dauerH: o.dauerH * scale }; };
  window.__mengeOverride = state.mengeOverride;
  const planOps = useMemo(() => plan.map(nr => byNr[nr]).filter(Boolean).map(o => ({ ...effOp(o), earliestH: arrivals[o.nr] ?? 0, __locked: lockedSet.has(o.nr) })), [plan, byNr, durchlaufData, lockedSet, state.mengeOverride]);
  const allOrders = tasks;
  const allCandidates = useMemo(() => tasks.filter(o => !o.released), [tasks]);
  // untere Tabellen: Zulauf-Aufträge, die (noch) nicht oben eingeplant sind
  const bottomCandidates = useMemo(() => allCandidates.filter(o => !inPlan.has(o.nr)), [allCandidates, inPlan]);
  const placedCount = useMemo(() => planOps.filter(o => !o.released).length, [planOps]);

  // gesamte obere Belegung (alle Aufträge oben) — kapazitätsbewusst
  const planSched = useMemo(() => window.computeSchedule(planOps, { gapFill: state.gapFill }), [planOps, state.setupMatrix, capModel, splitSet, state.gapFill]);

  // Frozen Zone dynamisch: N *offene* Schichten in die Zukunft (geschlossene Tage werden übersprungen)
  const frozenEndH = useMemo(() => {
    const openS = window.__openShifts;
    const target = state.frozenShifts;
    let count = 0;
    for (let i = 0; i < 3000; i++) {
      const d = Math.floor(i / 3);
      const open = openS ? openS(d) : 3;
      if ((i % 3) < open) { count++; if (count >= target) return (i + 1) * window.SHIFT_HOURS; }
    }
    return target * window.SHIFT_HOURS;
  }, [state.frozenShifts, capModel]);

  const posMap = useMemo(() => {
    const m = {};
    plan.forEach((nr, i) => { m[nr] = i + 1; });
    return m;
  }, [plan]);

  // KPIs — über ALLE Aufträge in der oberen Reihenfolge
  const kpi = useMemo(() => {
    if (!planOps.length) return null;
    const setupTotal = planSched.blocks.reduce((a, b) => a + b.setupMin, 0);
    const makespanShifts = Math.ceil((planSched.total - 0.001) / window.SHIFT_HOURS);
    const makespanDays = planSched.total / (window.SHIFT_HOURS * 3);
    const onTime = planOps.filter((o, i) => { if (!planSched.blocks[i]) return false; const DAY = window.SHIFT_HOURS * 3; const due = (Math.floor(o.planEnde / DAY) + 1) * DAY; return planSched.blocks[i].end <= due + 0.001; }).length;
    const violations = planSched.blocks.filter(b => b.op._forbidden).length;
    return { setupTotal, makespanShifts, makespanDays, onTime, total: planOps.length, violations };
  }, [planOps, planSched]);

  const solve = () => {
    const lockedOrder = plan.filter(nr => lockedSet.has(nr));
    const lockedOps = lockedOrder.map(nr => byNr[nr]).filter(Boolean);
    const toSeq = [...plan.filter(nr => !lockedSet.has(nr)).map(nr => byNr[nr]).filter(Boolean), ...bottomCandidates];
    const res = window.runSolver({
      frozen: lockedOps, candidates: toSeq, goal, restrictions: state.restrictions,
      weights: state.weights, kundeMap: state.kundeMap,
    });
    const newPlan = [...lockedOrder, ...res.sequence.map(o => o.nr)];
    setPlanState(() => ({ plan: newPlan }));
    set({ solving: true });
    setTimeout(() => set({ solving: false }), 1100);
  };

  // Optimierer: Greedy-Startlösung + lokale Suche (Or-opt) unter Beibehaltung der Fixierungen
  const optimize = () => {
    const lockedOrder = plan.filter(nr => lockedSet.has(nr));
    const lockedOps = lockedOrder.map(nr => byNr[nr]).filter(Boolean);
    const toSeq = [...plan.filter(nr => !lockedSet.has(nr)).map(nr => byNr[nr]).filter(Boolean), ...bottomCandidates];
    const res = window.runSolver({ frozen: lockedOps, candidates: toSeq, goal, restrictions: state.restrictions, weights: state.weights, kundeMap: state.kundeMap });
    const greedyOrder = [...lockedOrder, ...res.sequence.map(o => o.nr)];
    const opsSeq = greedyOrder.map(nr => ({ ...effOp(byNr[nr]), earliestH: arrivals[nr] ?? 0, __locked: lockedSet.has(nr) }));
    const opt = window.optimizeSequence(opsSeq, goal);
    setPlanState(() => ({ plan: opt.order }));
    const d = opt.delta;
    set({ solving: true, optMsg: `Optimiert vs. Greedy: Rüsten ${fmtPct(d.setup)} · Durchlaufzeit ${fmtPct(d.makespan)}${(d.tard < -0.5 || d.tard > 0.5) ? ` · Verspätung ${fmtPct(d.tard)}` : ''}` });
    setTimeout(() => set({ solving: false }), 900);
    setTimeout(() => set({ optMsg: null }), 8000);
  };
  const fmtPct = (x) => (x <= 0 ? '' : '+') + x.toFixed(0) + '%';

  // Manuelle Steuerung der oberen Reihenfolge
  const toggleLock = (nr) => setPlanState(c => ({ locked: c.locked.includes(nr) ? c.locked.filter(x => x !== nr) : [...c.locked, nr] }));
  const reorderPlan = (nr, beforeNr) => setPlanState(c => {
    const added = !c.plan.includes(nr);
    let p = c.plan.filter(x => x !== nr);
    let idx = beforeNr ? p.indexOf(beforeNr) : p.length;
    if (idx < 0) idx = p.length;
    p.splice(idx, 0, nr);
    const lk = added && !c.locked.includes(nr) ? [...c.locked, nr] : c.locked;
    return { plan: p, locked: lk };
  });
  const removeFromPlan = (nr) => setPlanState(c => ({ plan: c.plan.filter(x => x !== nr), locked: c.locked.filter(x => x !== nr) }));
  const toggleSplit = (nr) => setPlanState(c => ({ splittable: c.splittable.includes(nr) ? c.splittable.filter(x => x !== nr) : [...c.splittable, nr] }));

  // Systembezogene Handler (Durchlaufplanung — gilt für beliebiges Arbeitssystem)
  const reorderPlanFor = (wsId, nr, beforeNr) => set(s => {
    const plan = (s.planBySystem[wsId] || []).slice();
    if (!plan.includes(nr)) return s;
    const from = plan.indexOf(nr);
    plan.splice(from, 1);
    let idx = beforeNr ? plan.indexOf(beforeNr) : plan.length;
    if (idx < 0) idx = plan.length;
    plan.splice(idx, 0, nr);
    return { ...s, planBySystem: { ...s.planBySystem, [wsId]: plan } };
  });
  const toggleSplitFor = (wsId, nr) => set(s => {
    const sp = s.splittableBySystem[wsId] || [];
    return { ...s, splittableBySystem: { ...s.splittableBySystem, [wsId]: sp.includes(nr) ? sp.filter(x => x !== nr) : [...sp, nr] } };
  });

  // Einplanung (des aktiven Systems) auf Ausgangszustand zurücksetzen
  const resetPlan = () => {
    const released = (window.TASKS_BY_SYSTEM[sysId] || []).filter(t => t.released).map(t => t.nr);
    setPlanState(() => ({ plan: released, locked: released, splittable: [] }));
    set({ selected: null, solving: false });
    setExpanded(new Set());
  };

  const ws = window.WORK_SYSTEMS.find(w => w.id === state.workSystem);
  const goalSum = goal.durchlauf + goal.ruesten + goal.termin;

  // Solver für alle Arbeitssysteme (für die Durchlaufplanung)
  const solveAll = () => {
    setState(s => {
      const pbs = { ...s.planBySystem };
      window.WORK_SYSTEMS.forEach(w => {
        const plan = s.planBySystem[w.id] || [];
        const lockedS = new Set(s.lockedBySystem[w.id] || []);
        const tk = window.TASKS_BY_SYSTEM[w.id];
        const byNrT = Object.fromEntries(tk.map(t => [t.nr, t]));
        const inPlanS = new Set(plan);
        const lockedOrder = plan.filter(nr => lockedS.has(nr));
        const lockedOps = lockedOrder.map(nr => byNrT[nr]).filter(Boolean);
        const cands = [...plan.filter(nr => !lockedS.has(nr)).map(nr => byNrT[nr]).filter(Boolean), ...tk.filter(t => !inPlanS.has(t.nr))];
        const res = window.runSolver({ frozen: lockedOps, candidates: cands, goal: s.goalBySystem[w.id], restrictions: s.restrictions, weights: s.weights, kundeMap: s.kundeMap });
        pbs[w.id] = [...lockedOrder, ...res.sequence.map(o => o.nr)];
      });
      return { ...s, planBySystem: pbs, solving: true };
    });
    setTimeout(() => set({ solving: false }), 900);
  };

  return (
    <div style={{ height: '100vh', display: 'flex' }}>
      <Sidebar page={page} setPage={setPage} />
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }}>
      {page === 'durchlauf' && (
        <DurchlaufView data={durchlaufData} selected={state.selected} onSelect={(nr, ws) => set(ws && nr ? { selected: nr, workSystem: ws } : { selected: nr })} onSolveAll={solveAll} capacityBySystem={state.capacityBySystem} splitBySystem={state.splittableBySystem} onReorder={reorderPlanFor} onToggleSplit={toggleSplitFor} onUndo={undo} canUndo={histLen > 0} />
      )}
      {page === 'feinplanung' && (<>
      {/* ===== Kopfleiste ===== */}
      <header style={{ flex: '0 0 auto', display: 'flex', alignItems: 'center', gap: 18, padding: '0 18px', height: 56, background: 'var(--panel)', borderBottom: '1px solid var(--line-2)' }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 9 }}>
          <span style={{ fontSize: 14.5, fontWeight: 600 }}>Feinplanung</span>
        </div>

        {/* Arbeitssystem-Auswahl */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <L>Arbeitssystem</L>
          <select value={state.workSystem} onChange={e => set({ workSystem: e.target.value, selected: null })}
            style={{ ...selStyle, fontSize: 13, fontFamily: 'var(--sans)', fontWeight: 500, padding: '6px 10px' }}>
            {window.WORK_SYSTEMS.map(w => <option key={w.id} value={w.id}>{w.name}</option>)}
          </select>
          <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--muted)' }}>{ws.id} · {ws.kap}</span>
        </div>

        {/* Optimierungsziel — Kurzübersicht, öffnet Admin */}
        <button onClick={() => openAdmin('einplanung')} title="Optimierungsziel im Admin anpassen"
          style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 12, height: 36, padding: '0 12px',
            borderRadius: 8, border: '1px solid var(--line-2)', background: 'var(--surface)', cursor: 'pointer' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <Icon name="target" size={14} style={{ color: 'var(--muted)' }} />
            <L>Optimierungsziel</L>
          </span>
          {[
            { k: 'durchlauf', t: 'Durchlauf' },
            { k: 'ruesten', t: 'Rüsten' },
            { k: 'termin', t: 'Termin' },
          ].map(g => {
            const pct = Math.round(goal[g.k] / (goalSum || 1) * 100);
            return (
              <span key={g.k} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ fontSize: 11.5, color: 'var(--ink-2)' }}>{g.t}</span>
                <span style={{ position: 'relative', width: 42, height: 5, borderRadius: 3, background: 'var(--line)', overflow: 'hidden' }}>
                  <span style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: pct + '%', background: 'var(--accent-ink)' }} />
                </span>
                <span className="num" style={{ fontFamily: 'var(--mono)', fontSize: 10.5, color: 'var(--muted)', width: 26, textAlign: 'right' }}>{pct}%</span>
              </span>
            );
          })}
        </button>

        <div style={{ display: 'flex', gap: 9, marginLeft: 4 }}>
          <IconBtn icon="play" label="Berechnen" primary onClick={solve} />
          <IconBtn icon="target" label="Optimieren" onClick={optimize} />
          <IconBtn icon={state.gapFill ? 'check' : 'clock'} label="Lücken füllen" active={state.gapFill} onClick={() => set({ gapFill: !state.gapFill })} />
          <IconBtn icon="undo" label="Undo" onClick={undo} disabled={histLen === 0} />
          <IconBtn icon="reset" label="Reset" onClick={resetPlan} />
          <IconBtn icon="settings" label="Admin" active={admin} onClick={() => openAdmin('einplanung')} />
        </div>
      </header>

      {/* ===== Arbeitsfläche ===== */}
      <main style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateRows: '1fr 1fr', gridTemplateColumns: 'minmax(0, 1fr)', gap: 12, padding: 12 }}>
        {/* Obere Ansicht: Eingeplante Reihenfolge über volle Breite — Drop-Ziel für manuelles Fixieren */}
        <div onDragOver={e => { if (e.dataTransfer.types.includes('text/plain')) { e.preventDefault(); if (!dragOver) setDragOver(true); } }}
          onDragLeave={e => { if (!e.currentTarget.contains(e.relatedTarget)) setDragOver(false); }}
          onDrop={e => { e.preventDefault(); const nr = e.dataTransfer.getData('text/plain'); if (nr) reorderPlan(nr, null); setDragOver(false); }}
          style={{ minHeight: 0, minWidth: 0, position: 'relative', outline: dragOver ? '2px dashed var(--accent-ink)' : 'none', outlineOffset: -2, borderRadius: 8 }}>
          <PanelShell title="Eingeplante Reihenfolge" sub={state.optMsg ? state.optMsg : (placedCount ? `${placedCount} eingeplant · ${locked.length} fixiert` : 'Vorgänge aus dem Zulauf hierher ziehen oder „Berechnen“')} icon="flag"
            right={kpi && <KpiStrip kpi={kpi} />}>
            <div style={{ padding: '12px 14px 4px', flex: '0 0 auto' }}>
              <Gantt blocks={planSched.blocks} frozenEndH={frozenEndH} solving={state.solving} lockedSet={lockedSet} selected={state.selected} onSelect={nr => set({ selected: nr })} />
            </div>
            <div style={{ overflow: 'auto', flex: 1, padding: '2px 4px 0' }}>
              <SequenceTable blocks={planSched.blocks} solving={state.solving} lockedSet={lockedSet} onToggleLock={toggleLock} onReorder={reorderPlan} onRemove={removeFromPlan} splitSet={splitSet} onToggleSplit={toggleSplit} selected={state.selected} onSelect={nr => set({ selected: nr })} onLosClick={op => setLosOp(op)} />
              {placedCount === 0 && bottomCandidates.length > 0 && (
                <div style={{ textAlign: 'center', padding: '14px 0 20px', color: 'var(--muted)', fontSize: 12.5 }}>
                  <Icon name="arrow" size={16} style={{ opacity: .5 }} /> {bottomCandidates.length} Arbeitsvorgänge im Zulauf — hochziehen zum Fixieren oder „Berechnen“ für die automatische Reihenfolge.
                </div>
              )}
            </div>
          </PanelShell>
          {dragOver && (
            <div style={{ position: 'absolute', inset: 0, background: 'color-mix(in oklch, var(--accent) 30%, transparent)', borderRadius: 8, display: 'grid', placeItems: 'center', pointerEvents: 'none', zIndex: 5 }}>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '8px 16px', background: 'var(--panel)', border: '1px solid var(--accent-ink)', borderRadius: 8, fontSize: 13, fontWeight: 600, color: 'var(--accent-ink)', boxShadow: 'var(--shadow)' }}>
                <Icon name="lock" size={15} /> Hier ablegen, um zu fixieren
              </span>
            </div>
          )}
        </div>

        {/* Untere Ansicht */}
        <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 5fr) minmax(0, 7fr)', gap: 12, minHeight: 0 }}>
          <OperationsTable candidates={allOrders} selected={state.selected} onSelect={nr => set({ selected: nr })} posMap={posMap} onDragRow={reorderPlan} />
          <OrdersTable candidates={allOrders} selected={state.selected} onSelect={nr => set({ selected: nr })} posMap={posMap} expanded={expanded} onToggleExpand={toggleExpand} onDragRow={reorderPlan} currentWs={state.workSystem} />
        </div>
      </main>
      </>)}
      </div>

      <AdminPanel open={admin} onClose={() => setAdmin(false)} state={state} set={set} initial={adminPage} />
      {losOp && <AndlerPopup op={losOp} onClose={() => setLosOp(null)} onApply={(nr, menge) => { set(s => ({ ...s, mengeOverride: { ...s.mengeOverride, [nr]: menge } })); setLosOp(null); }} />}
    </div>
  );
}

function Sidebar({ page, setPage }) {
  const items = [
    { id: 'feinplanung', label: 'Feinplanung', icon: 'sliders' },
    { id: 'durchlauf', label: 'Durchlaufplanung', icon: 'layers' },
  ];
  return (
    <aside style={{ width: 212, flex: '0 0 auto', background: 'var(--surface)', borderRight: '1px solid var(--line-2)', display: 'flex', flexDirection: 'column' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '16px 18px', borderBottom: '1px solid var(--line)' }}>
        <span style={{ fontWeight: 700, fontSize: 16, letterSpacing: '-.01em' }}>deep<span style={{ color: 'var(--accent-ink)' }}>Ing</span></span>
      </div>
      <nav style={{ padding: 10, display: 'flex', flexDirection: 'column', gap: 4 }}>
        {items.map(it => {
          const on = page === it.id;
          return (
            <button key={it.id} onClick={() => setPage(it.id)} style={{
              display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 8, textAlign: 'left', cursor: 'pointer',
              border: '1px solid ' + (on ? 'var(--accent-line)' : 'transparent'),
              background: on ? 'var(--accent)' : 'transparent', color: on ? 'var(--accent-ink)' : 'var(--ink-2)',
              fontWeight: on ? 600 : 500, fontSize: 13.5, transition: 'all .12s' }}>
              <Icon name={it.icon} size={17} /> {it.label}
            </button>
          );
        })}
      </nav>
      <div style={{ marginTop: 'auto', padding: 14, borderTop: '1px solid var(--line)' }}>
        <div className="label" style={{ fontSize: 9 }}>Modul</div>
        <div style={{ fontSize: 11.5, color: 'var(--ink-2)', marginTop: 2 }}>Auftragsfeinplanung</div>
      </div>
    </aside>
  );
}

function KpiStrip({ kpi }) {
  const item = (label, val, tone) => (
    <div style={{ textAlign: 'right' }}>
      <div className="num" style={{ fontFamily: 'var(--mono)', fontSize: 14, fontWeight: 600, color: tone || 'var(--ink)' }}>{val}</div>
      <div className="label" style={{ fontSize: 9 }}>{label}</div>
    </div>
  );
  return (
    <div style={{ display: 'flex', gap: 20, alignItems: 'center' }}>
      {item('Σ Rüsten', kpi.setupTotal + ' min')}
      {item('Durchlaufzeit', kpi.makespanDays.toFixed(1).replace('.', ',') + ' Tage')}
      {item('Termintreue', kpi.onTime + '/' + kpi.total, kpi.onTime === kpi.total ? 'var(--good-ink)' : 'var(--warn-ink)')}
      {kpi.violations > 0 && item('Verstöße', kpi.violations, 'var(--bad-ink)')}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
