/* gantt.jsx — "Plán úloh": interaktívny Gantt čiastkových úloh tiketu (úrovne 1–3).
   1) Plánovateľné okno úlohy — ťahanie celého pruhu = presun, ťahanie okrajov = zmena
      začiatku/termínu. Bez plánu ostáva auto-odvodený (skutočný) pruh ako doteraz.
      + "Plán vs skutočnosť": plánovaný pruh navrchu, tenký skutočný pruh pod ním.
   2) Čiara Dnes + čiara SLA termínu, víkendové tieňovanie (konzistentné s pauzou SLA),
      farba pruhu podľa technika, pridanie/úprava úlohy priamo v pláne.
   3) Zoom (priblíženie/oddialenie/prispôsobiť), preusporiadanie riadkov ťahaním,
      odhad trvania pri vytváraní úlohy.
   Dáta = len ďalšie JSON polia v stĺpci Ulohy (planZaciatok/planKoniec) — bez zmeny SP. */

const G_HOUR = 3600000, G_DEN = 86400000;
const G_LEFT_W = 210, G_HEADER_H = 34, G_ROW_H = 54, G_ADD_H = 74;
const G_SNAP = G_HOUR;        // ťahanie prichytáva na celé hodiny
const G_MINDUR = 2 * G_HOUR;  // najkratší plán
const G_MINZ = 12, G_MAXZ = 600; // px na deň (zoom)

const gClamp = (v, a, b) => Math.max(a, Math.min(b, v));
const gSnap = (t) => Math.round(t / G_SNAP) * G_SNAP;
const gDen0 = (t) => { const d = new Date(t); d.setHours(0, 0, 0, 0); return d.getTime(); };
const gFmtD = (t) => new Date(t).toLocaleDateString("sk-SK", { day: "2-digit", month: "2-digit" });
const gFmtDT = (t) => new Date(t).toLocaleString("sk-SK", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });

/* ---------- jeden ťahateľný pruh (plán + skutočnosť) ---------- */
function GanttBar({ u, actual, plan, hasPlan, farba, now, msPerPx, xOf, top, onCommit }) {
  const [prev, setPrev] = useState(null); // náhľad počas ťahania (nekomituje, kým nepustíš)
  const dragRef = useRef(null);

  const start = prev ? prev.s : plan.s;
  const koniec = prev ? prev.e : plan.e;
  const poTermine = !u.hotovo && koniec < now;
  const barFarba = u.hotovo ? "var(--green)" : (poTermine ? "var(--red)" : farba);

  const onDown = (e) => {
    e.preventDefault();
    const rect = e.currentTarget.getBoundingClientRect();
    const off = e.clientX - rect.left;
    // Úzky pruh (kratší než ~30px) nemá miesto na okrajové zóny — vždy sa presúva celý.
    // Širší pruh: krajných 10px = zmena začiatku/termínu, stred = presun.
    const mode = rect.width < 30 ? "move" : (off < 10 ? "l" : off > rect.width - 10 ? "r" : "move");
    dragRef.current = { mode, startX: e.clientX, s0: start, e0: koniec };
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
    setPrev({ s: start, e: koniec });
  };
  const onMove = (e) => {
    const d = dragRef.current; if (!d) return;
    const delta = (e.clientX - d.startX) * msPerPx;
    let s = d.s0, en = d.e0;
    if (d.mode === "move") { s = gSnap(d.s0 + delta); en = gSnap(d.e0 + delta); }
    else if (d.mode === "l") { s = Math.min(gSnap(d.s0 + delta), d.e0 - G_MINDUR); en = d.e0; }
    else { en = Math.max(gSnap(d.e0 + delta), d.s0 + G_MINDUR); s = d.s0; }
    setPrev({ s, e: en });
  };
  const onUp = (e) => {
    const d = dragRef.current; dragRef.current = null;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
    if (d && prev) onCommit(prev.s, prev.e);
    setPrev(null);
  };

  const barTop = top + (hasPlan ? 6 : 15);
  return (
    <>
      {/* plánovaný / hlavný pruh (ťahateľný) */}
      <div onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp}
        title={gFmtDT(start) + " – " + gFmtDT(koniec) + (prev ? "" : " · ťahaj = presun, okraje = zmena")}
        style={{
          position: "absolute", top: barTop, left: xOf(start),
          width: Math.max((koniec - start) / msPerPx, 10), height: 16,
          background: "color-mix(in srgb, " + barFarba + " 22%, transparent)",
          border: "1.5px solid " + barFarba, borderRadius: 6,
          cursor: "grab", touchAction: "none", zIndex: 2,
          boxShadow: prev ? "var(--shadow-md)" : "none",
        }}>
        <span style={{ position: "absolute", left: 2, top: 3, bottom: 3, width: 2, borderRadius: 2, background: barFarba, opacity: .5 }} />
        <span style={{ position: "absolute", right: 2, top: 3, bottom: 3, width: 2, borderRadius: 2, background: barFarba, opacity: .5 }} />
      </div>

      {/* skutočný pruh (len ak existuje plán — na porovnanie plán vs realita) */}
      {hasPlan && (
        <div title={"Skutočnosť: " + gFmtDT(actual.s) + " – " + (u.hotovo ? gFmtDT(actual.e) : "dnes")}
          style={{
            position: "absolute", top: top + 27, left: xOf(actual.s),
            width: Math.max((actual.e - actual.s) / msPerPx, 4), height: 7,
            background: u.hotovo ? "var(--green)" : "color-mix(in srgb, " + farba + " 55%, transparent)",
            borderRadius: 4, zIndex: 1,
          }} />
      )}

      {/* dátumový štítok za pruhom */}
      <div style={{
        position: "absolute", top: barTop, left: xOf(koniec) + 8, height: 16,
        display: "flex", alignItems: "center", fontSize: 10.5, color: "var(--text-3)",
        whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums", zIndex: 2, pointerEvents: "none",
      }}>
        {gFmtD(start)} – {u.hotovo && !prev ? gFmtD(actual.e) : gFmtD(koniec)}
      </div>
    </>
  );
}

/* ---------- ľavý stĺpec: riadok úlohy (checkbox, názov, s kým, grip) ---------- */
function GanttRow({ u, i, leftW, technici, actions, dragging, isOver, onGripDown, onGripMove, onGripUp }) {
  const [edit, setEdit] = useState(false);
  const [val, setVal] = useState(u.text);
  const [kym, setKym] = useState(false);
  useEffect(() => { setVal(u.text); }, [u.text]);
  const ulozit = () => { setEdit(false); const v = val.trim(); if (v && v !== u.text) actions.upravitTextUlohy(u.id, v); else setVal(u.text); };
  const farbaTechnika = (m) => { const t = technici.find((x) => x.meno === m); return t ? t.farba : "#69797e"; };

  return (
    <div style={{
      position: "sticky", left: 0, width: leftW, height: G_ROW_H, zIndex: kym ? 20 : 2,
      background: "var(--surface)", borderRight: "1px solid var(--stroke)", borderBottom: "1px solid var(--stroke)",
      opacity: dragging ? .4 : 1, boxShadow: isOver ? "inset 0 2px 0 var(--accent)" : "none",
      display: "flex", flexDirection: "column", justifyContent: "center", padding: "0 8px", gap: 5,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <button onPointerDown={(e) => onGripDown(e, i, u.id)} onPointerMove={onGripMove} onPointerUp={onGripUp}
          title="Presunúť poradie (ťahaj)" style={{ flex: "0 0 auto", cursor: "grab", touchAction: "none", border: "none", background: "transparent", color: "var(--text-3)", padding: 0, display: "inline-flex" }}>
          <Icon name="grip" size={15} />
        </button>
        <button onClick={() => actions.prepnutUlohu(u.id)} title={u.hotovo ? "Označiť ako nesplnené" : "Označiť ako splnené"} style={{
          flex: "0 0 auto", width: 17, height: 17, borderRadius: 5, display: "inline-flex", alignItems: "center", justifyContent: "center",
          border: "1.5px solid " + (u.hotovo ? "var(--green)" : "var(--stroke-strong)"), background: u.hotovo ? "var(--green)" : "transparent", color: "#fff", padding: 0,
        }}>
          {u.hotovo && <Icon name="check" size={11} />}
        </button>
        {edit ? (
          <input autoFocus value={val} onChange={(e) => setVal(e.target.value)} onBlur={ulozit}
            onKeyDown={(e) => { if (e.key === "Enter") ulozit(); if (e.key === "Escape") { setVal(u.text); setEdit(false); } }}
            style={{ flex: 1, minWidth: 0, fontSize: 12.5, fontFamily: "inherit", border: "1px solid var(--accent)", borderRadius: 4, padding: "1px 5px", background: "var(--surface-2)", color: "var(--text)" }} />
        ) : (
          <span onClick={() => setEdit(true)} title={u.text} style={{
            flex: 1, minWidth: 0, fontSize: 12.5, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", cursor: "text",
            color: u.hotovo ? "var(--text-3)" : "var(--text)", textDecoration: u.hotovo ? "line-through" : "none",
          }}>{u.text}</span>
        )}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 3, paddingLeft: 21 }}>
        {(u.sKym || []).map((m) => <Avatar key={m} meno={m} farba={farbaTechnika(m)} size={17} photoKey={m} />)}
        <button onClick={() => setKym((v) => !v)} title="S kým som to riešil" style={{
          width: 17, height: 17, borderRadius: "50%", display: "inline-flex", alignItems: "center", justifyContent: "center",
          border: "1px dashed var(--stroke-strong)", background: kym ? "var(--surface-3)" : "transparent", color: "var(--text-3)", padding: 0,
        }}>
          <Icon name="plus" size={10} />
        </button>
        <button onClick={() => actions.zmazatUlohu(u.id)} title="Zmazať úlohu" style={{ marginLeft: "auto", border: "none", background: "transparent", color: "var(--text-3)", display: "inline-flex", padding: 2 }}>
          <Icon name="close" size={12} />
        </button>
      </div>
      {kym && (
        <div style={{ position: "absolute", top: G_ROW_H - 6, left: 8, width: leftW - 16, zIndex: 30, background: "var(--surface)", border: "1px solid var(--stroke)", borderRadius: "var(--radius-sm)", boxShadow: "var(--shadow-md)", padding: 8 }}>
          <PersonChips hodnoty={u.sKym || []} onZmena={(arr) => actions.zmenaKymUlohy(u.id, arr)} placeholder="Pridať kolegu…" m365 />
        </div>
      )}
    </div>
  );
}

/* ---------- pridanie úlohy priamo v pláne (s voliteľným odhadom trvania) ---------- */
function GanttAdd({ actions, leftW }) {
  const [text, setText] = useState("");
  const [dur, setDur] = useState("");
  const opts = [
    { l: "bez odhadu", ms: "" }, { l: "1 h", ms: G_HOUR }, { l: "2 h", ms: 2 * G_HOUR },
    { l: "4 h", ms: 4 * G_HOUR }, { l: "1 deň", ms: G_DEN }, { l: "3 dni", ms: 3 * G_DEN },
  ];
  const pridat = () => {
    const v = text.trim(); if (!v) return;
    if (dur !== "") actions.pridatUlohu(v, { planZaciatok: Date.now(), planKoniec: Date.now() + Number(dur) });
    else actions.pridatUlohu(v);
    setText("");
  };
  return (
    <div style={{ position: "sticky", left: 0, width: leftW, minHeight: G_ADD_H, zIndex: 2, background: "var(--surface-2)", borderRight: "1px solid var(--stroke)", padding: "8px", display: "flex", flexDirection: "column", gap: 6 }}>
      <div style={{ display: "flex", gap: 5 }}>
        <input value={text} onChange={(e) => setText(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") pridat(); }} placeholder="Pridať úlohu…"
          style={{ flex: 1, minWidth: 0, fontSize: 12.5, fontFamily: "inherit", border: "1px solid var(--stroke-strong)", borderRadius: "var(--radius-sm)", padding: "5px 8px", background: "var(--surface)", color: "var(--text)" }} />
        <button onClick={pridat} title="Pridať úlohu" style={{ flex: "0 0 auto", border: "none", background: "var(--accent)", color: "var(--accent-on)", borderRadius: "var(--radius-sm)", width: 30, display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
          <Icon name="plus" size={15} />
        </button>
      </div>
      <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, color: "var(--text-3)" }}>
        <Icon name="clock" size={12} /> Odhad:
        <select value={dur === "" ? "" : String(dur)} onChange={(e) => setDur(e.target.value)}
          style={{ flex: 1, minWidth: 0, fontSize: 11.5, fontFamily: "inherit", border: "1px solid var(--stroke-strong)", borderRadius: "var(--radius-sm)", padding: "3px 6px", background: "var(--surface)", color: "var(--text-2)" }}>
          {opts.map((o, i) => <option key={i} value={o.ms === "" ? "" : String(o.ms)}>{o.l}</option>)}
        </select>
      </label>
    </div>
  );
}

function GBtn({ icon, title, onClick, active }) {
  return (
    <button onClick={onClick} title={title} style={{
      width: 30, height: 30, borderRadius: "var(--radius-sm)", border: "1px solid var(--stroke)",
      background: active ? "var(--accent-tint)" : "var(--surface)", color: active ? "var(--accent)" : "var(--text-2)",
      display: "inline-flex", alignItems: "center", justifyContent: "center", padding: 0,
    }}>
      <Icon name={icon} size={15} />
    </button>
  );
}

function GLegend({ label, typ }) {
  let mark;
  if (typ === "plan") mark = <span style={{ width: 16, height: 11, borderRadius: 3, border: "1.5px solid var(--accent)", background: "color-mix(in srgb, var(--accent) 22%, transparent)" }} />;
  else if (typ === "real") mark = <span style={{ width: 16, height: 6, borderRadius: 3, background: "color-mix(in srgb, var(--accent) 55%, transparent)" }} />;
  else if (typ === "today") mark = <span style={{ width: 2, height: 13, background: "var(--accent)" }} />;
  else mark = <span style={{ width: 0, height: 13, borderLeft: "2px dashed var(--red)" }} />;
  return <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>{mark}{label}</span>;
}

function GanttEmpty() {
  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 10, padding: 24, textAlign: "center" }}>
      <Icon name="chart" size={30} style={{ color: "var(--text-3)" }} />
      <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--text-2)" }}>Zatiaľ žiadne úlohy</div>
      <div style={{ fontSize: 12.5, color: "var(--text-3)", maxWidth: 300 }}>
        Pridaj čiastkovú úlohu nižšie (alebo v detaile tiketu). Pruh potom vieš ťahať myšou — presunúť ho v čase, alebo za okraje zmeniť začiatok a termín.
      </div>
    </div>
  );
}

/* ====================== HLAVNÝ GANTT ====================== */
function GanttPlan({ tiket, technici, actions, now }) {
  const ulohy = tiket.ulohy || [];
  const paneRef = useRef(null);
  const [availW, setAvailW] = useState(0);
  const [pxRaw, setPxRaw] = useState(null); // null = auto-prispôsobiť
  const [reorder, setReorder] = useState(null); // { id, i, over }
  const reRef = useRef(null);
  // šírka ľavého stĺpca — ťahateľná a zapamätaná (naprieč tiketmi)
  const [leftW, setLeftW] = useState(() => {
    const v = parseInt(localStorage.getItem("hd_gantt_leftw"), 10);
    return v >= 120 && v <= 640 ? v : G_LEFT_W;
  });
  const szRef = useRef(null);

  useEffect(() => {
    const el = paneRef.current; if (!el) return;
    const mer = () => setAvailW(el.clientWidth);
    mer(); window.addEventListener("resize", mer);
    return () => window.removeEventListener("resize", mer);
  }, []);

  if (!ulohy.length) return <GanttEmpty />;

  // ---- časová mierka ----
  const casy = [now];
  if (tiket.termin) casy.push(tiket.termin);
  ulohy.forEach((u) => {
    casy.push(u.vytvorene || now, u.hotoveKedy || now);
    if (u.planZaciatok != null) casy.push(u.planZaciatok);
    if (u.planKoniec != null) casy.push(u.planKoniec);
  });
  let tMin = gDen0(Math.min.apply(null, casy));
  let tMax = gDen0(Math.max.apply(null, casy)) + G_DEN;
  if (tMax - tMin < 3 * G_DEN) tMax = tMin + 3 * G_DEN;
  const days = (tMax - tMin) / G_DEN;
  const fitVal = gClamp(Math.max(280, (availW || 640) - leftW - 6) / days, G_MINZ, G_MAXZ);
  const pxPerDay = pxRaw == null ? fitVal : gClamp(pxRaw, G_MINZ, G_MAXZ);
  const msPerPx = G_DEN / pxPerDay;
  const timelineW = days * pxPerDay;
  const xOf = (t) => (t - tMin) / msPerPx; // 0-based v rámci časovej osi

  // ---- delenie osi ----
  const cand = [G_HOUR, 3 * G_HOUR, 6 * G_HOUR, 12 * G_HOUR, G_DEN, 2 * G_DEN, 7 * G_DEN, 14 * G_DEN, 30 * G_DEN];
  const step = cand.find((c) => (c / G_DEN) * pxPerDay >= 74) || cand[cand.length - 1];
  const ticks = [];
  for (let t = tMin; t <= tMax; t += step) ticks.push(t);
  const tickLbl = (t) => {
    const d = new Date(t);
    if (step >= G_DEN) return gFmtD(t);
    return (d.getHours() === 0 && d.getMinutes() === 0) ? gFmtD(t) : d.toLocaleTimeString("sk-SK", { hour: "2-digit", minute: "2-digit" });
  };

  // ---- víkendové pásy ----
  const bands = [];
  for (let d = tMin, k = 0; d < tMax && k < 400; d += G_DEN, k++) {
    const wd = new Date(d).getDay();
    if (wd === 0 || wd === 6) bands.push(d);
  }

  const farbaTechnika = (m) => { const t = technici.find((x) => x.meno === m); return t ? t.farba : null; };
  const barFarba = (u) => (u.sKym && u.sKym.length && farbaTechnika(u.sKym[0])) || "var(--accent)";

  const chartH = ulohy.length * G_ROW_H;
  const innerW = leftW + timelineW;
  const innerH = G_HEADER_H + chartH + G_ADD_H;

  // ---- ťahanie oddeľovača = zmena šírky ľavého stĺpca ----
  const szMax = Math.max(160, (availW || 640) - 160);
  const onSzDown = (e) => {
    e.preventDefault();
    szRef.current = { startX: e.clientX, w0: leftW };
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
  };
  const onSzMove = (e) => {
    const s = szRef.current; if (!s) return;
    setLeftW(gClamp(s.w0 + (e.clientX - s.startX), 120, szMax));
  };
  const onSzUp = (e) => {
    if (!szRef.current) return;
    szRef.current = null;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
    try { localStorage.setItem("hd_gantt_leftw", String(leftW)); } catch (_) {}
  };

  // ---- preusporiadanie ťahaním za grip ----
  const onGripDown = (e, i, id) => {
    e.preventDefault();
    reRef.current = { id, i, startY: e.clientY };
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
    setReorder({ id, i, over: i });
  };
  const onGripMove = (e) => {
    const r = reRef.current; if (!r) return;
    const dRows = Math.round((e.clientY - r.startY) / G_ROW_H);
    setReorder({ id: r.id, i: r.i, over: gClamp(r.i + dRows, 0, ulohy.length - 1) });
  };
  const onGripUp = (e) => {
    const r = reRef.current; reRef.current = null;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
    const over = reorder ? reorder.over : (r ? r.i : 0);
    setReorder(null);
    if (r && over !== r.i) actions.presunUlohu(r.id, over);
  };

  return (
    <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", background: "var(--bg)" }}>
      {/* panel nástrojov */}
      <div style={{ flex: "0 0 auto", display: "flex", alignItems: "center", gap: 16, padding: "10px 16px", borderBottom: "1px solid var(--stroke)", flexWrap: "wrap" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 3 }}>
          <GBtn icon="minus" title="Oddialiť" onClick={() => setPxRaw(gClamp((pxRaw == null ? fitVal : pxRaw) / 1.6, G_MINZ, G_MAXZ))} />
          <GBtn icon="plus" title="Priblížiť" onClick={() => setPxRaw(gClamp((pxRaw == null ? fitVal : pxRaw) * 1.6, G_MINZ, G_MAXZ))} />
          <GBtn icon="arrowsX" title="Prispôsobiť šírke" onClick={() => setPxRaw(null)} active={pxRaw == null} />
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 14, fontSize: 11.5, color: "var(--text-2)", flexWrap: "wrap" }}>
          <GLegend label="Plán" typ="plan" />
          <GLegend label="Skutočnosť" typ="real" />
          <GLegend label="Dnes" typ="today" />
          <GLegend label="SLA termín" typ="sla" />
        </div>
      </div>

      {/* scroller — obidve osi; ľavý stĺpec sticky-left, hlavička sticky-top */}
      <div ref={paneRef} style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
        <div style={{ position: "relative", width: innerW, minHeight: innerH }}>

          {/* hlavička s dátumovou osou */}
          <div style={{ position: "sticky", top: 0, height: G_HEADER_H, zIndex: 4, background: "var(--surface)", borderBottom: "1px solid var(--stroke)" }}>
            <div style={{ position: "sticky", left: 0, width: leftW, height: G_HEADER_H, display: "flex", alignItems: "center", padding: "0 12px", fontSize: 11, fontWeight: 700, color: "var(--text-3)", textTransform: "uppercase", letterSpacing: ".4px", background: "var(--surface)", borderRight: "1px solid var(--stroke)", zIndex: 5 }}>
              Úloha
              {/* oddeľovač — potiahnutím meníš šírku stĺpca (zapamätá sa) */}
              <div onPointerDown={onSzDown} onPointerMove={onSzMove} onPointerUp={onSzUp}
                title="Potiahnutím zmeníš šírku stĺpca úloh"
                style={{ position: "absolute", top: 0, right: -5, width: 10, height: innerH, cursor: "ew-resize", touchAction: "none", zIndex: 8, display: "flex", justifyContent: "center" }}>
                <span style={{ width: 2, height: "100%", background: "color-mix(in srgb, var(--stroke-strong) 55%, transparent)" }} />
              </div>
            </div>
            {ticks.map((t, i) => (
              <div key={"t" + i} style={{ position: "absolute", top: 0, left: leftW + xOf(t), height: G_HEADER_H, display: "flex", alignItems: "center", fontSize: 10.5, color: "var(--text-3)", fontVariantNumeric: "tabular-nums", transform: "translateX(3px)", pointerEvents: "none", whiteSpace: "nowrap" }}>{tickLbl(t)}</div>
            ))}
            <div style={{ position: "absolute", top: 0, left: leftW + xOf(now), height: G_HEADER_H, display: "flex", alignItems: "center", pointerEvents: "none" }}>
              <span style={{ fontSize: 9.5, fontWeight: 700, color: "var(--accent)", background: "var(--surface)", padding: "0 3px", transform: "translateX(3px)" }}>dnes</span>
            </div>
            {tiket.termin && (
              <div style={{ position: "absolute", top: 0, left: leftW + xOf(tiket.termin), height: G_HEADER_H, display: "flex", alignItems: "center", pointerEvents: "none" }}>
                <span style={{ fontSize: 9.5, fontWeight: 700, color: "var(--red)", background: "var(--surface)", padding: "0 3px", transform: "translateX(4px)" }}>SLA</span>
              </div>
            )}
          </div>

          {/* pozadie: víkendy, mriežka, riadky, dnes, SLA */}
          <div style={{ position: "absolute", top: G_HEADER_H, left: leftW, width: timelineW, height: chartH, zIndex: 0 }}>
            {bands.map((d, i) => (
              <div key={"w" + i} style={{ position: "absolute", top: 0, bottom: 0, left: xOf(d), width: pxPerDay, background: "color-mix(in srgb, var(--text-3) 7%, transparent)" }} />
            ))}
            {ticks.map((t, i) => (
              <div key={"g" + i} style={{ position: "absolute", top: 0, bottom: 0, left: xOf(t), width: 1, background: "var(--stroke)" }} />
            ))}
            {ulohy.map((_, i) => (
              <div key={"r" + i} style={{ position: "absolute", left: 0, right: 0, top: (i + 1) * G_ROW_H, height: 1, background: "var(--stroke)" }} />
            ))}
            <div style={{ position: "absolute", top: 0, bottom: 0, left: xOf(now), width: 2, background: "var(--accent)" }} />
            {tiket.termin && (
              <div style={{ position: "absolute", top: 0, bottom: 0, left: xOf(tiket.termin), width: 0, borderLeft: "2px dashed var(--red)" }} />
            )}
          </div>

          {/* pruhy úloh */}
          {ulohy.map((u, i) => {
            const actual = { s: u.vytvorene || tMin, e: u.hotoveKedy || now };
            const hasPlan = u.planZaciatok != null && u.planKoniec != null;
            const plan = hasPlan
              ? { s: u.planZaciatok, e: u.planKoniec }
              : { s: actual.s, e: Math.max(actual.e, actual.s + G_MINDUR) };
            return (
              <GanttBar key={u.id} u={u} actual={actual} plan={plan} hasPlan={hasPlan}
                farba={barFarba(u)} now={now} msPerPx={msPerPx}
                xOf={(t) => leftW + xOf(t)} top={G_HEADER_H + i * G_ROW_H}
                onCommit={(s, e) => actions.upravitPlanUlohy(u.id, s, e)} />
            );
          })}

          {/* ľavý stĺpec — riadky úloh (sticky-left) */}
          {ulohy.map((u, i) => (
            <GanttRow key={u.id} u={u} i={i} leftW={leftW} technici={technici} actions={actions}
              dragging={!!reorder && reorder.id === u.id}
              isOver={!!reorder && reorder.over === i && reorder.id !== u.id}
              onGripDown={onGripDown} onGripMove={onGripMove} onGripUp={onGripUp} />
          ))}

          {/* pridanie úlohy */}
          <GanttAdd actions={actions} leftW={leftW} />
        </div>
      </div>
    </div>
  );
}

window.GanttPlan = GanttPlan;
