/* Модуль «График работ» — гантт из works (даты, статус, зависимости FS/SS/FF).
   Данные/права из общего стора. Цены/деньги тут не показываются (роль-агностично). */
window.RD = window.RD || { core: {}, modules: {} };
window.RD.modules.Schedule = function Schedule() {
  var store = RD.core.useStore();
  var C = RD.core, h = React.createElement;
  var srRef = React.useState(false); var showRejected = srRef[0], setShowRejected = srRef[1];
  var works = (store.raw.works || []).filter(function (w) { return showRejected || !w.rejected; });
  var proj = store.raw.project || {};
  var todayD = proj.todayDay != null ? proj.todayDay : 11;

  // горизонт: max end
  var maxEnd = works.reduce(function (m, w) { return Math.max(m, w.end || 0); }, 1);
  var totalDays = maxEnd + 2;
  var scaleRef = React.useState('day'); var scale = scaleRef[0], setScale = scaleRef[1];
  var viewRef = React.useState('gantt'); var ganttView = viewRef[0], setGanttView = viewRef[1];
  var dayW = ({ day: 22, '3day': 12, week: 9, decade: 6, month: 3.2, quarter: 1.4 })[scale] || 22, rowH = 34, barTop = 7, barH = 20, labelW = 250, headerH = 34;
  var sX = function (d) { return d * dayW; };
  var gTotalW = totalDays * dayW;

  // drag-перетаскивание баров → каскадный пересчёт (как ganttDrag* монолита)
  var dgRef = React.useState(null); var dragWorks = dgRef[0], setDragWorks = dgRef[1];
  var dragMeta = React.useRef(null);
  React.useEffect(function () {
    function move(e) {
      var dm = dragMeta.current; if (!dm) return;
      var dd = Math.round((e.clientX - dm.x0) / dayW); if (dd === dm.moved) return; dm.moved = dd;
      var wks;
      if (dm.edge === 'right') { var ne = Math.max(dm.start0, dm.end0 + dd); wks = (store.raw.works || []).map(function (w) { return w.id === dm.id ? Object.assign({}, w, { end: ne }) : Object.assign({}, w); }); }
      else if (dm.edge === 'left') { var nst = Math.max(0, Math.min(dm.end0, dm.start0 + dd)); wks = (store.raw.works || []).map(function (w) { return w.id === dm.id ? Object.assign({}, w, { start: nst }) : Object.assign({}, w); }); }
      else { var ns = Math.max(0, dm.start0 + dd), du = dm.end0 - dm.start0; wks = (store.raw.works || []).map(function (w) { return w.id === dm.id ? Object.assign({}, w, { start: ns, end: ns + du }) : Object.assign({}, w); }); }
      wks = C.propagateSchedule(wks, dm.id, lockSet);
      dm.result = wks; setDragWorks(wks);
    }
    function up() {
      var dm = dragMeta.current; if (!dm) return; dragMeta.current = null;
      var res = dm.result, moved = dm.moved;
      setDragWorks(null);
      if (moved !== 0 && res) {
        if (!store.perms.schedule) { store.showToast('Недостаточно прав: роль «' + store.perms.role + '» не двигает график', 'warn'); return; }
        store.pushHist();
        var nd = Object.assign({}, store.raw); nd.works = res;
        store.replaceEntity('works', res);
        store.showToast('Работа сдвинута на ' + (moved > 0 ? '+' : '') + moved + ' дн · пересчитано ' + (C._lastMoves || 0) + ' работ', 'ok');
      }
    }
    window.addEventListener('mousemove', move); window.addEventListener('mouseup', up);
    return function () { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
  }, [store]);
  function startDrag(w, e) { dragMeta.current = { id: w.id, x0: e.clientX, start0: w.start, end0: w.end, moved: 0, result: null, edge: null }; e.preventDefault(); e.stopPropagation(); }
  function startResize(w, e, edge) { dragMeta.current = { id: w.id, x0: e.clientX, start0: w.start, end0: w.end, moved: 0, result: null, edge: edge }; e.preventDefault(); e.stopPropagation(); }
  var pfRef = React.useState(false); var planFact = pfRef[0], setPlanFact = pfRef[1];
  var blRef = React.useState(false); var showBase = blRef[0], setShowBase = blRef[1];
  var wdRef = React.useState(null); var workDrawer = wdRef[0], setWorkDrawer = wdRef[1];
  var lmRef = React.useState(false); var linkMode = lmRef[0], setLinkMode = lmRef[1];
  var lfRef = React.useState(null); var linkFrom = lfRef[0], setLinkFrom = lfRef[1];
  var ltRef = React.useState('FS'); var linkType = ltRef[0], setLinkType = ltRef[1];
  var hovRef = React.useState(null); var hoverW = hovRef[0], setHoverW = hovRef[1];
  var scnRef = React.useState(null); var scenario = scnRef[0], setScenario = scnRef[1];
  var lockRef = React.useState([]); var locked = lockRef[0], setLocked = lockRef[1];
  var lockSet = {}; locked.forEach(function (id) { lockSet[id] = 1; });
  var lyRef = React.useState({ pay: false, del: false, labor: false, cash: false }); var layers = lyRef[0], setLayers = lyRef[1];
  function toggleLayer(k) { var n = Object.assign({}, layers); n[k] = !n[k]; setLayers(n); }
  var selRef = React.useState([]); var sel = selRef[0], setSel = selRef[1];
  var peRef = React.useState(null); var pendingEdit = peRef[0], setPendingEdit = peRef[1];
  var prRef = React.useState(''); var peReason = prRef[0], setPeReason = prRef[1];
  function toggleSel(id) { setSel(sel.indexOf(id) >= 0 ? sel.filter(function (x) { return x !== id; }) : sel.concat([id])); }
  function clearSel() { setSel([]); }
  function bulkShift(delta) {
    if (!sel.length) return;
    if (!store.perms.schedule) { store.showToast('Недостаточно прав: роль «' + store.perms.role + '» не двигает график', 'warn'); return; }
    store.pushHist();
    var nd = (store.raw.works || []).map(function (w) { return sel.indexOf(w.id) >= 0 ? Object.assign({}, w, { start: Math.max(0, w.start + delta), end: Math.max(1, w.end + delta) }) : Object.assign({}, w); });
    nd = C.propagateSchedule(nd, null, lockSet);
    store.replaceEntity('works', nd);
    store.showToast('Сдвинуто ' + sel.length + ' работ на ' + (delta > 0 ? '+' : '') + delta + ' дн · каскад пересчитан', 'ok');
  }
  function bulkBrigade() { if (!sel.length) return; store.showToast('Назначение бригады для ' + sel.length + ' работ — в проде выбор из ресурсов', 'ok'); }
  function editWorkField(id, field, val) {
    if (!store.perms.schedule) { store.showToast('Недостаточно прав: роль «' + store.perms.role + '» не правит график', 'warn'); return; }
    var w0 = (store.raw.works || []).find(function (x) { return x.id === id; }); if (!w0) return;
    var patch = {};
    if (field === 'start') { var ns = parseInt(val, 10); if (isNaN(ns) || ns < 0) return; var du = w0.end - w0.start; patch = { start: ns, end: ns + du }; }
    else if (field === 'dur') { var d = Math.max(1, parseInt(val, 10) || 1); patch = { end: w0.start + d - 1 }; }
    else if (field === 'name') { if (!('' + val).trim()) return; patch = { name: ('' + val).trim() }; }
    else return;
    // правка факта/прошлого → подтверждение
    var isFact = (w0.status === 'done' || w0.status === 'in_progress');
    var inPast = (patch.start != null && patch.start < todayD);
    if (isFact || inPast) { setPendingEdit({ id: id, field: field, patch: patch, name: w0.name, reason: isFact ? 'правка факта идущей/завершённой работы' : 'начало в прошлом (раньше «сегодня»)' }); return; }
    store.pushHist();
    var nd = (store.raw.works || []).map(function (w) { return w.id === id ? Object.assign({}, w, patch) : Object.assign({}, w); });
    nd = C.propagateSchedule(nd, null, lockSet);
    store.replaceEntity('works', nd);
    store.showToast('Изменено · каскад пересчитан', 'ok');
  }
  function commitPendingEdit(reasonText) {
    var pe = pendingEdit; if (!pe) return;
    store.pushHist();
    var nd = (store.raw.works || []).map(function (w) { return w.id === pe.id ? Object.assign({}, w, pe.patch) : Object.assign({}, w); });
    nd = C.propagateSchedule(nd, null, lockSet);
    store.replaceEntity('works', nd);
    setPendingEdit(null);
    store.showToast('Подтверждено' + (reasonText ? ' · причина: ' + reasonText : '') + ' · запись в журнал', 'ok');
  }
  function projEnd(wks) { return Math.max.apply(null, (wks || []).filter(function (w) { return !w.rejected; }).map(function (w) { return w.end; })); }
  function enterScenario() { setScenario({ end0: projEnd(store.raw.works), snap: JSON.stringify(store.raw.works) }); store.showToast('Режим «что-если»: меняйте график, сравнивайте срок; примите или отбросьте', 'ok'); }
  function discardScenario() { if (scenario && scenario.snap) { store.replaceEntity('works', JSON.parse(scenario.snap)); } setScenario(null); store.showToast('Сценарий отброшен', 'warn'); }
  function acceptScenario() { setScenario(null); store.showToast('Сценарий принят как новый план · чекпоинт', 'ok'); }
  function toggleLock(id) { var has = locked.indexOf(id) >= 0; setLocked(has ? locked.filter(function (x) { return x !== id; }) : locked.concat([id])); store.showToast('Дата работы ' + (has ? 'разблокирована' : 'зафиксирована (констрейнт «не двигать»)'), 'ok'); }
  function applyWorks(nd, primaryId) { var res = C.propagateSchedule(nd, null, lockSet); store.replaceEntity('works', res); return res; }
  function setDepType(workId, predId, t) { store.pushHist(); var nd = (store.raw.works || []).map(function (w) { return w.id === workId ? Object.assign({}, w, { deps: (w.deps || []).map(function (d) { return d.f === predId ? Object.assign({}, d, { t: t }) : d; }) }) : Object.assign({}, w); }); applyWorks(nd, workId); store.showToast('Тип связи: ' + t + ' · график пересчитан', 'ok'); }
  function setDepLag(workId, predId, v) { store.pushHist(); var nd = (store.raw.works || []).map(function (w) { return w.id === workId ? Object.assign({}, w, { deps: (w.deps || []).map(function (d) { return d.f === predId ? Object.assign({}, d, { lag: v }) : d; }) }) : Object.assign({}, w); }); applyWorks(nd, workId); store.showToast('Лаг связи: ' + (v > 0 ? '+' + v : v) + ' дн · пересчитано', 'ok'); }
  function removeDep(workId, predId) { store.pushHist(); var nd = (store.raw.works || []).map(function (w) { return w.id === workId ? Object.assign({}, w, { deps: (w.deps || []).filter(function (d) { return d.f !== predId; }) }) : Object.assign({}, w); }); applyWorks(nd, workId); store.showToast('Связь удалена · пересчитано', 'ok'); }
  function chooseXor(keepId, dropId) { store.pushHist(); var nd = (store.raw.works || []).map(function (w) { if (w.id === keepId) return Object.assign({}, w, { rejected: false, status: w.status === 'alt' ? 'planned' : w.status }); if (w.id === dropId) return Object.assign({}, w, { rejected: true, status: 'alt' }); return Object.assign({}, w); }); store.replaceEntity('works', C.propagateSchedule(nd, null, lockSet)); store.showToast('Выбран вариант · альтернатива отклонена, график пересчитан', 'ok'); }
  function reaches(wks, from, target) { var byId = {}; wks.forEach(function (w) { byId[w.id] = w; }); var stack = [from], seen = {}; while (stack.length) { var cur = stack.pop(); if (cur === target) return true; if (seen[cur]) continue; seen[cur] = 1; (byId[cur] && byId[cur].deps || []).forEach(function (d) { stack.push(d.f); }); } return false; }
  function pickForLink(id) {
    if (!linkFrom) { setLinkFrom(id); store.showToast('Предшественник выбран · кликните зависимую работу', 'ok'); return; }
    if (linkFrom === id) { setLinkFrom(null); store.showToast('Связь отменена', 'warn'); return; }
    var wks0 = store.raw.works || [], target = wks0.find(function (w) { return w.id === id; });
    if ((target.deps || []).some(function (d) { return d.f === linkFrom; })) { setLinkMode(false); setLinkFrom(null); store.showToast('Такая связь уже есть', 'warn'); return; }
    if (reaches(wks0, linkFrom, id)) { setLinkMode(false); setLinkFrom(null); store.showToast('Нельзя — возникнет цикл зависимостей', 'warn'); return; }
    if (reaches(wks0, id, linkFrom)) { setLinkMode(false); setLinkFrom(null); store.showToast('Избыточная связь: уже зависит через цепочку', 'warn'); return; }
    store.pushHist();
    var nd = wks0.map(function (w) { return w.id === id ? Object.assign({}, w, { deps: (w.deps || []).concat([{ f: linkFrom, t: linkType, lag: 0 }]) }) : Object.assign({}, w); });
    nd = C.propagateSchedule(nd, null);
    store.replaceEntity('works', nd);
    setLinkMode(false); setLinkFrom(null);
    store.showToast('Связь ' + linkType + ' добавлена · график пересчитан', 'ok');
  }
  function chainOf(wid) {
    var byId = {}; (store.raw.works || []).forEach(function (w) { byId[w.id] = w; });
    var up = {}, down = {};
    (function climb(id) { (byId[id] && byId[id].deps || []).forEach(function (d) { if (!up[d.f]) { up[d.f] = 1; climb(d.f); } }); })(wid);
    (function desc(id) { (store.raw.works || []).forEach(function (w) { if ((w.deps || []).some(function (d) { return d.f === id; }) && !down[w.id]) { down[w.id] = 1; desc(w.id); } }); })(wid);
    return { up: up, down: down };
  }
  var baseRef = React.useRef(null);
  function toggleBaseline() { if (!baseRef.current) { baseRef.current = (store.raw.works || []).map(function (w) { return { id: w.id, start: w.start, end: w.end }; }); store.showToast('Базовый план зафиксирован (baseline)', 'ok'); setShowBase(true); } else setShowBase(!showBase); }
  if (dragWorks) { var dwById = {}; dragWorks.forEach(function (w) { dwById[w.id] = w; }); works = works.map(function (w) { return dwById[w.id] || w; }); }

  // критический путь: работы с нулевым резервом (упрощённо — самая длинная цепочка по FS)
  var byId = {}; works.forEach(function (w) { byId[w.id] = w; });
  var crit = {};
  // прямой проход: ES; обратный: LS; slack=0 → крит.
  var ES = {}, EF = {}, dur = {};
  works.forEach(function (w) { dur[w.id] = (w.end - w.start + 1); });
  works.forEach(function (w) {
    var es = 0;
    (w.deps || []).forEach(function (d) { var p = byId[d.f]; if (!p) return; var lag = d.lag || 0; var c = d.t === 'SS' ? (ES[d.f] || p.start) + lag : d.t === 'FF' ? (EF[d.f] || (p.end + 1)) - dur[w.id] + lag : (EF[d.f] || (p.end + 1)) + lag; if (c > es) es = c; });
    ES[w.id] = es || w.start; EF[w.id] = (es || w.start) + dur[w.id];
  });
  var projEnd = works.reduce(function (m, w) { return Math.max(m, EF[w.id]); }, 0);
  var LS = {}, LF = {};
  works.slice().reverse().forEach(function (w) { LF[w.id] = projEnd; });
  for (var pass = 0; pass < works.length; pass++) {
    works.forEach(function (w) {
      (w.deps || []).forEach(function (d) {
        var p = byId[d.f]; if (!p) return; var lag = d.lag || 0;
        var lf = d.t === 'SS' ? (LS[w.id] != null ? LS[w.id] : w.start) - lag + dur[d.f] : (LS[w.id] != null ? LS[w.id] : w.start) - lag;
        if (LF[d.f] == null || lf < LF[d.f]) LF[d.f] = lf;
      });
    });
  }
  works.forEach(function (w) { LS[w.id] = (LF[w.id] != null ? LF[w.id] : projEnd) - dur[w.id]; if ((LS[w.id] - (ES[w.id] != null ? ES[w.id] : w.start)) <= 0) crit[w.id] = true; });

  var stColor = function (w) { return w.status === 'done' ? 'var(--rd-good)' : w.status === 'in_progress' ? 'var(--rd-accent)' : w.status === 'alt' ? '#cbb994' : '#aeb0cf'; };
  var vi = {}; works.forEach(function (w, i) { vi[w.id] = i; });

  // svg-связи
  var links = [];
  works.forEach(function (w) {
    (w.deps || []).forEach(function (d) {
      if (vi[d.f] == null) return;
      var p = byId[d.f], pi = vi[d.f], si = vi[w.id];
      var sy = pi * rowH + barTop + barH / 2, ty = si * rowH + barTop + barH / 2;
      var pX1 = (p.end + 1) * dayW, pX0 = p.start * dayW, cX0 = w.start * dayW, cX1 = (w.end + 1) * dayW;
      var sx = d.t === 'SS' ? pX0 : pX1, tx = (d.t === 'FF') ? cX1 : cX0;
      var isC = crit[p.id] && crit[w.id], col = isC ? 'var(--rd-bad)' : '#bcbcca', stub = 7;
      links.push({ d: 'M ' + sx + ' ' + sy + ' L ' + (sx + stub) + ' ' + sy + ' L ' + (sx + stub) + ' ' + ty + ' L ' + tx + ' ' + ty, col: col, w: isC ? 1.7 : 1.1 });
    });
  });

  var ticks = [];
  for (var t2 = 0; t2 <= totalDays; t2 += 7) ticks.push(t2);

  // ===== ДОРОЖКИ-СЛОИ (этапы → оплаты/поставки/раб.сила/cash) =====
  var allW = (store.raw.works || []).filter(function (w) { return !w.rejected; });
  var stageMap = {};
  allW.forEach(function (w) { var st = w.stage || '—'; if (!stageMap[st]) stageMap[st] = { name: st, start: w.start, end: w.end }; else { stageMap[st].start = Math.min(stageMap[st].start, w.start); stageMap[st].end = Math.max(stageMap[st].end, w.end); } });
  var stages = Object.keys(stageMap).map(function (k) { return stageMap[k]; });
  var stColors = ['#3357e6', '#c2790a', '#0a7ea4', '#2f8f5b', '#7a5af0', '#9a6a06', '#5f6b8f'];
  stages.forEach(function (s, i) { s.color = stColors[i % stColors.length]; });
  // суммы по этапам — из payments (по полю stage), иначе пропорция
  function stageAmt(name) { var ps = (store.raw.payments || []).filter(function (p) { return p.stage === name; }); return ps.reduce(function (a, p) { return a + (p.amount || 0); }, 0); }
  var payEv = [], delEv = [];
  stages.forEach(function (s) {
    var amt = stageAmt(s.name), adv = Math.round(amt * 0.4), comp = amt - adv;
    delEv.push({ day: Math.max(0, s.start - 2), color: s.color, text: 'Поставка', sub: '→ старт «' + s.name + '» (−2д)' });
    if (adv) payEv.push({ day: s.start, color: s.color, type: 'Аванс', amt: adv, sub: 'старт «' + s.name + '»' });
    if (comp) payEv.push({ day: Math.min(totalDays - 1, s.end + 1), color: s.color, type: 'По завершении', amt: comp, sub: 'сдача «' + s.name + '»' });
  });
  var paySumVis = payEv.reduce(function (a, e) { return a + e.amt; }, 0);
  // рабочая сила по дням (trade × crew)
  var laborDay = []; var laborPeak = 0;
  for (var d2 = 0; d2 < totalDays; d2++) { var tot = 0, seg = {}; allW.forEach(function (w) { if (d2 >= w.start && d2 <= w.end) { var c = w.crew || 1; tot += c; seg[w.trade || '—'] = (seg[w.trade || '—'] || 0) + c; } }); laborDay.push({ d: d2, tot: tot, seg: seg }); if (tot > laborPeak) laborPeak = tot; }
  // resource leveling: перегрузка по специальности (норма 4)
  var TRADE_CAP = 4, overloads = {};
  laborDay.forEach(function (pd) { Object.keys(pd.seg).forEach(function (tr) { if (pd.seg[tr] > TRADE_CAP) { overloads[tr] = Math.max(overloads[tr] || 0, pd.seg[tr]); } }); });
  var levelKeys = Object.keys(overloads);
  var levelText = levelKeys.length ? ('Перегрузка: ' + levelKeys.map(function (tr) { return tr + ' до ' + overloads[tr] + ' чел (норма ' + TRADE_CAP + ')'; }).join(' · ')) : '';
  // cash-flow накопительно
  var cashCum = [], cum = 0; var cashTotal = paySumVis || 1;
  for (var d3 = 0; d3 < totalDays; d3++) { cum += payEv.filter(function (e) { return e.day === d3; }).reduce(function (a, e) { return a + e.amt; }, 0); cashCum.push(cum); }
  // weekend bands (день 0 = вт, упрощённо: сб/вс каждые 7)
  var weekends = [];
  for (var d4 = 0; d4 < totalDays; d4++) { var dow = (d4 + 2) % 7; if (dow === 5 || dow === 6) weekends.push(d4); }
  var moneyShort = function (n) { return n >= 1000 ? Math.round(n / 1000) + 'к' : '' + n; };
  var laneHeadStyle = { display: 'flex', alignItems: 'center', gap: 8, height: 26, background: 'var(--rd-surface-3)', borderTop: '1px solid var(--rd-border)', borderBottom: '1px solid var(--rd-border)', padding: '0 12px', fontSize: 10.5, fontWeight: 700, letterSpacing: .3, color: 'var(--rd-text-2)', position: 'sticky', left: 0 };

  var LAYER_DEFS = [['pay', 'Оплаты'], ['del', 'Поставки'], ['labor', 'Раб.сила'], ['cash', 'Финансы']];

  return h('div', { className: 'rd-main-content', style: { background: 'var(--rd-surface)' } },
    h('div', { style: { height: 46, flex: 'none', borderBottom: '1px solid var(--rd-border)', display: 'flex', alignItems: 'center', padding: '0 16px', gap: 12, background: 'var(--rd-surface-2)' } },
      h('div', { style: { fontSize: 13, fontWeight: 600 } }, 'График работ'),
      h('div', { className: 'mono', style: { fontSize: 11.5, color: 'var(--rd-text-muted)' } }, works.length + ' работ · ' + links.length + ' связей · крит. путь ' + Object.keys(crit).length),
      h('div', { style: { display: 'flex', padding: 2, background: 'var(--rd-surface-3)', borderRadius: 7, gap: 2 } },
        [['gantt', 'Гантт'], ['table', 'Таблица']].map(function (v) { return h('button', { key: v[0], onClick: function () { setGanttView(v[0]); }, style: { padding: '4px 10px', border: 'none', borderRadius: 6, background: ganttView === v[0] ? 'var(--rd-surface)' : 'transparent', color: ganttView === v[0] ? 'var(--rd-text)' : 'var(--rd-text-muted)', fontSize: 11, fontWeight: ganttView === v[0] ? 600 : 500, cursor: 'pointer', boxShadow: ganttView === v[0] ? '0 1px 2px rgba(0,0,0,.08)' : 'none' } }, v[1]); })),
      h('div', { style: { flex: 1 } }),
      h('button', { onClick: function () { setShowRejected(!showRejected); }, title: 'Показать/скрыть отклонённые (XOR-альтернативы)', style: { padding: '5px 10px', border: '1px solid var(--rd-border)', borderRadius: 7, background: showRejected ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: showRejected ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 11, fontWeight: showRejected ? 600 : 500, cursor: 'pointer' } }, showRejected ? '⊘ Скрыть отклонённые' : '⊘ Показать отклонённые'),
      h('button', { onClick: function () { setPlanFact(!planFact); }, style: { padding: '5px 10px', border: '1px solid var(--rd-border)', borderRadius: 7, background: planFact ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: planFact ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 11, fontWeight: planFact ? 600 : 500, cursor: 'pointer' } }, 'План/Факт'),
      h('button', { onClick: toggleBaseline, style: { padding: '5px 10px', border: '1px solid var(--rd-border)', borderRadius: 7, background: showBase ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: showBase ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 11, fontWeight: showBase ? 600 : 500, cursor: 'pointer' } }, 'Baseline'),
      h('div', { style: { display: 'flex', gap: 2 } }, [['day', 'Дни'], ['week', 'Нед'], ['month', 'Мес'], ['quarter', 'Кв']].map(function (s) { return h('button', { key: s[0], onClick: function () { setScale(s[0]); }, style: { padding: '5px 8px', border: '1px solid var(--rd-border)', borderRadius: 6, background: scale === s[0] ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: scale === s[0] ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 11, cursor: 'pointer' } }, s[1]); })),
      h('button', { onClick: function () { setLinkMode(!linkMode); setLinkFrom(null); store.showToast(linkMode ? 'Режим связывания выключен' : 'Режим связи: кликните предшественника, затем зависимую', 'ok'); }, style: { padding: '5px 10px', border: '1px solid var(--rd-border)', borderRadius: 7, background: linkMode ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: linkMode ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 11, fontWeight: linkMode ? 600 : 500, cursor: 'pointer' } }, '⛓ Связать'),
      scenario ? h('span', { style: { display: 'flex', gap: 4, alignItems: 'center' } },
        h('span', { className: 'mono', style: { fontSize: 11, fontWeight: 700, color: projEnd(works) > scenario.end0 ? 'var(--rd-bad)' : 'var(--rd-good)' } }, (function () { var d = projEnd(works) - scenario.end0; return (d > 0 ? '+' + d : d) + ' дн к сроку'; })()),
        h('button', { onClick: acceptScenario, style: { padding: '5px 9px', border: 'none', borderRadius: 7, background: 'var(--rd-good)', color: '#fff', fontSize: 11, fontWeight: 600, cursor: 'pointer' } }, 'Принять'),
        h('button', { onClick: discardScenario, style: { padding: '5px 9px', border: '1px solid var(--rd-border)', borderRadius: 7, background: 'var(--rd-surface)', color: 'var(--rd-text-2)', fontSize: 11, cursor: 'pointer' } }, 'Отбросить'))
        : h('button', { onClick: enterScenario, style: { padding: '5px 10px', border: '1px solid var(--rd-border)', borderRadius: 7, background: 'var(--rd-surface)', color: 'var(--rd-text-2)', fontSize: 11, fontWeight: 500, cursor: 'pointer' } }, '⎇ Что-если'),
      linkMode ? h('div', { style: { display: 'flex', gap: 2 } }, ['FS', 'SS', 'FF'].map(function (t) { return h('button', { key: t, onClick: function () { setLinkType(t); }, style: { padding: '5px 7px', border: '1px solid var(--rd-border)', borderRadius: 6, background: linkType === t ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: linkType === t ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 10.5, fontWeight: 600, cursor: 'pointer' } }, t); })) : null,
      h('span', { style: { fontSize: 10, color: 'var(--rd-text-muted)', marginLeft: 4 } }, 'Слои:'),
      LAYER_DEFS.map(function (l) { return h('button', { key: l[0], onClick: function () { toggleLayer(l[0]); }, style: { padding: '4px 8px', border: '1px solid ' + (layers[l[0]] ? 'var(--rd-accent)' : 'var(--rd-border)'), borderRadius: 6, background: layers[l[0]] ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: layers[l[0]] ? 'var(--rd-accent)' : 'var(--rd-text-muted)', fontSize: 10.5, fontWeight: layers[l[0]] ? 600 : 500, cursor: 'pointer' } }, l[1]); })
    ),
    ganttView === 'table' ? (function () {
      // все столбцы из БД: собираем объединение ключей по всем работам
      var keys = []; (store.raw.works || []).forEach(function (w) { Object.keys(w).forEach(function (k) { if (keys.indexOf(k) < 0) keys.push(k); }); });
      var editable = { name: 1, start: 1 };
      var fmt = function (v) { if (v == null) return ''; if (Array.isArray(v)) return v.map(function (d) { return d && d.t ? (d.t + (d.lag ? (d.lag > 0 ? '+' + d.lag : d.lag) : '') + '←' + d.f) : JSON.stringify(d); }).join(' · '); if (typeof v === 'object') return JSON.stringify(v); return '' + v; };
      var colW = function (k) { return k === 'name' ? 'minmax(170px,1.4fr)' : k === 'deps' ? '150px' : (k === 'compare' ? '220px' : '92px'); };
      var grid = '44px ' + keys.map(colW).join(' ');
      return h('div', { className: 'rd-gantt-wrap', style: { overflow: 'auto' } },
        h('div', { style: { padding: '6px 12px', fontSize: 10.5, color: 'var(--rd-text-muted)' } }, 'Все столбцы из БД: ' + keys.length + ' · строк: ' + works.length + (store.perms.schedule ? ' · правка: имя, начало' : ' · только просмотр (роль ' + store.perms.role + ')')),
        h('div', { style: { minWidth: 60 + keys.length * 96 } },
          h('div', { style: { display: 'grid', gridTemplateColumns: grid, background: 'var(--rd-surface-3)', borderBottom: '1px solid var(--rd-border)', fontSize: 10, fontWeight: 600, color: 'var(--rd-text-muted)', position: 'sticky', top: 0, zIndex: 2 } },
            [h('div', { key: 'n', style: { padding: '8px 8px' } }, '№')].concat(keys.map(function (k) { return h('div', { key: k, style: { padding: '8px 8px' } }, k + (editable[k] && store.perms.schedule ? ' ✎' : '')); }))),
          works.map(function (w, i) {
            return h('div', { key: w.id, style: { display: 'grid', gridTemplateColumns: grid, borderBottom: '1px solid var(--rd-border)', fontSize: 11.5, alignItems: 'center', background: w.rejected ? 'var(--rd-bad-soft,#fbeaea)' : (i % 2 ? 'var(--rd-surface-2)' : 'var(--rd-surface)') } },
              [h('div', { key: 'n', className: 'mono', style: { padding: '5px 8px', color: 'var(--rd-text-muted)', fontSize: 10 } }, i + 1)].concat(keys.map(function (k) {
                if (editable[k] && store.perms.schedule) return h('input', { key: k, defaultValue: w[k], onBlur: function (e) { if (('' + e.target.value) !== ('' + w[k])) editWorkField(w.id, k, e.target.value); }, style: { padding: '5px 8px', border: 'none', background: 'transparent', fontSize: 11.5, color: 'var(--rd-text)', width: '100%', fontFamily: k === 'name' ? 'inherit' : 'IBM Plex Mono, monospace' } });
                return h('div', { key: k, className: (typeof w[k] === 'number' || k === 'deps' || k === 'id') ? 'mono' : '', style: { padding: '5px 8px', color: 'var(--rd-text-2)', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, fmt(w[k]));
              })));
          })
        )
      );
    })() : h('div', { className: 'rd-gantt-wrap' },
      h('div', { style: { width: labelW + gTotalW, position: 'relative' } },
        h('div', { style: { display: 'flex', position: 'sticky', top: 0, zIndex: 5, height: headerH, background: 'var(--rd-surface-2)', borderBottom: '1px solid var(--rd-border)' } },
          h('div', { style: { position: 'sticky', left: 0, width: labelW, flex: 'none', background: 'var(--rd-surface-2)', borderRight: '1px solid var(--rd-border)', display: 'flex', alignItems: 'center', padding: '0 12px', fontSize: 10.5, fontWeight: 600, color: 'var(--rd-text-muted)', zIndex: 6 } }, 'РАБОТА · ЗАВИСИМОСТИ'),
          h('div', { style: { position: 'relative', flex: 1 } }, ticks.map(function (d) { return h('div', { key: d, className: 'mono', style: { position: 'absolute', left: sX(d), top: 0, height: headerH, borderLeft: '1px solid var(--rd-border)', paddingLeft: 4, fontSize: 10, color: 'var(--rd-text-muted)', display: 'flex', alignItems: 'center' } }, 'д' + d); }))
        ),
        // тело
        h('div', { style: { position: 'relative', height: works.length * rowH } },
          // выходные (затенение)
          weekends.map(function (d) { return h('div', { key: 'we' + d, style: { position: 'absolute', left: labelW + sX(d), top: 0, width: dayW, height: works.length * rowH, background: 'rgba(120,120,120,.06)', zIndex: 0 } }); }),
          // строки-лейблы
          works.map(function (w, i) {
            return h('div', { key: w.id, style: { position: 'absolute', top: i * rowH, left: 0, width: labelW + gTotalW, height: rowH, borderBottom: '1px solid var(--rd-border-soft, var(--rd-border))', background: i % 2 ? 'var(--rd-surface-2)' : 'var(--rd-surface)' } },
              h('div', { onMouseEnter: function () { setHoverW(w.id); }, onMouseLeave: function () { setHoverW(null); }, style: { position: 'sticky', left: 0, width: labelW, height: '100%', background: linkFrom === w.id ? 'var(--rd-accent-soft)' : (sel.indexOf(w.id) >= 0 ? 'var(--rd-accent-soft)' : (i % 2 ? 'var(--rd-surface-2)' : 'var(--rd-surface)')), borderRight: '1px solid var(--rd-border)', padding: '0 8px 0 6px', display: 'flex', alignItems: 'center', gap: 6, zIndex: 3 } },
                h('span', { onClick: function (e) { e.stopPropagation(); toggleSel(w.id); }, title: 'Выбрать для массовой операции', style: { width: 15, height: 15, flex: 'none', borderRadius: 4, border: '1.5px solid ' + (sel.indexOf(w.id) >= 0 ? 'var(--rd-accent)' : 'var(--rd-border)'), background: sel.indexOf(w.id) >= 0 ? 'var(--rd-accent)' : 'transparent', color: '#fff', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' } }, sel.indexOf(w.id) >= 0 ? '✓' : ''),
                h('div', { onClick: function () { if (linkMode) pickForLink(w.id); else setWorkDrawer(w.id); }, style: { cursor: 'pointer', flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', justifyContent: 'center' } },
                  h('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
                    h('span', { style: { width: 7, height: 7, borderRadius: '50%', background: stColor(w), flex: 'none' } }),
                    h('span', { style: { fontSize: 11.5, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, w.name),
                    crit[w.id] ? h('span', { style: { fontSize: 8, fontWeight: 700, color: 'var(--rd-bad)', background: 'var(--rd-bad-soft, #fbeaea)', padding: '1px 4px', borderRadius: 3, flex: 'none' } }, 'КП') : null),
                  h('div', { className: 'mono', style: { fontSize: 9.5, color: 'var(--rd-text-muted)', paddingLeft: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, (w.deps || []).length ? (w.deps || []).map(function (d) { return d.t + (d.lag ? (d.lag > 0 ? '+' + d.lag : d.lag) : '') + '←' + d.f; }).join(' · ') : 'старт'))
              )
            );
          }),
          // svg-связи
          h('svg', { style: { position: 'absolute', left: labelW, top: 0, width: gTotalW, height: works.length * rowH, overflow: 'visible', pointerEvents: 'none', zIndex: 1 } },
            links.map(function (lk, i) { return h('path', { key: i, d: lk.d, style: { fill: 'none', stroke: lk.col, strokeWidth: lk.w } }); })
          ),
          // бары
          works.map(function (w, i) {
            var x = sX(w.start), bw = (w.end - w.start + 1) * dayW;
            return h('div', { key: w.id, onMouseDown: function (e) { startDrag(w, e); }, title: w.name + ' · д' + w.start + '–д' + w.end, style: { position: 'absolute', left: labelW + x, top: i * rowH + barTop, width: bw, height: barH, cursor: 'grab', background: stColor(w), borderRadius: 5, display: 'flex', alignItems: 'center', padding: '0 6px', zIndex: 2, boxShadow: '0 1px 2px rgba(0,0,0,.12)', border: crit[w.id] ? '1.5px solid var(--rd-bad)' : 'none' } },
              h('span', { className: 'mono', style: { fontSize: 8.5, color: '#fff', fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden' } }, 'д' + w.start + '–' + w.end),
              h('span', { onMouseDown: function (e) { startResize(w, e, 'left'); }, style: { position: 'absolute', left: -2, top: 0, width: 8, height: '100%', cursor: 'ew-resize' } }),
              h('span', { onMouseDown: function (e) { startResize(w, e, 'right'); }, style: { position: 'absolute', right: -2, top: 0, width: 8, height: '100%', cursor: 'ew-resize' } }));
          }),
          // baseline-призрак (если зафиксирован)
          showBase && baseRef.current ? baseRef.current.map(function (bw, idx) {
            var i = works.findIndex(function (w) { return w.id === bw.id; }); if (i < 0) return null;
            return h('div', { key: 'bl' + bw.id, title: 'baseline', style: { position: 'absolute', left: labelW + sX(bw.start), top: i * rowH + barTop - 4, width: (bw.end - bw.start + 1) * dayW, height: 3, background: 'var(--rd-text-faint)', borderRadius: 2, zIndex: 1, opacity: .7 } });
          }) : null,
          // план/факт: фактический бар из w.af/w.ae под плановым
          planFact ? works.map(function (w, i) {
            if (w.af == null || w.ae == null) return null;
            var dev = w.ae - w.end;
            return h('div', { key: 'f' + w.id, title: 'факт д' + w.af + '–д' + w.ae + (dev ? ' (' + (dev > 0 ? '+' : '') + dev + 'д)' : ''), style: { position: 'absolute', left: labelW + sX(w.af), top: i * rowH + barTop + barH - 2, width: (w.ae - w.af + 1) * dayW, height: 5, background: dev > 0 ? 'var(--rd-bad)' : dev < 0 ? 'var(--rd-good)' : 'var(--rd-accent)', borderRadius: 3, zIndex: 3 } });
          }) : null,
          // линия сегодня
          h('div', { style: { position: 'absolute', left: labelW + sX(todayD), top: 0, height: works.length * rowH, width: 2, background: 'rgba(207,59,59,.45)', zIndex: 4 } })
        ),
        // resource-leveling баннер
        levelText ? h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '7px 12px', background: '#fdf6e7', borderTop: '1px solid #f0e3c0', borderBottom: '1px solid #f0e3c0', position: 'sticky', left: 0 } },
          h('span', { style: { fontSize: 9.5, fontWeight: 700, color: '#9a6a06', background: '#fbeaca', padding: '2px 7px', borderRadius: 5, flex: 'none' } }, 'RESOURCE LEVELING'),
          h('span', { style: { fontSize: 11, color: '#7d5a13', flex: 1, lineHeight: 1.35 } }, levelText),
          h('button', { onClick: function () { enterScenario(); store.showToast('Выровняйте пики в режиме «что-если»', 'ok'); }, style: { flex: 'none', fontSize: 11, fontWeight: 600, color: '#9a6a06', background: '#fbeaca', border: '1px solid #ecd49a', borderRadius: 7, padding: '5px 10px', cursor: 'pointer' } }, 'Выровнять →')) : null,
        // ДОРОЖКА: ОПЛАТЫ
        layers.pay ? h('div', null,
          h('div', { style: laneHeadStyle }, '◆ ОПЛАТЫ · ' + payEv.length + ' · Σ ' + moneyShort(paySumVis) + ' ₽'),
          h('div', { style: { position: 'relative', height: 44, background: 'var(--rd-surface)' } },
            weekends.map(function (d) { return h('div', { key: 'pw' + d, style: { position: 'absolute', left: labelW + sX(d), top: 0, width: dayW, height: 44, background: 'rgba(120,120,120,.06)' } }); }),
            payEv.map(function (e, i) { return h('div', { key: i, title: e.type + ' ' + moneyShort(e.amt) + '₽ · ' + e.sub, style: { position: 'absolute', left: labelW + sX(e.day), top: 8 + (i % 2) * 16, transform: 'translateX(-50%)', display: 'flex', alignItems: 'center', gap: 4, padding: '2px 6px', background: e.color, color: '#fff', borderRadius: 5, fontSize: 9, fontWeight: 600, whiteSpace: 'nowrap', zIndex: 2 } }, '◆ ' + moneyShort(e.amt) + 'к'); }),
            h('div', { style: { position: 'absolute', left: labelW + sX(todayD), top: 0, height: 44, width: 2, background: 'rgba(207,59,59,.3)' } }))) : null,
        // ДОРОЖКА: ПОСТАВКИ
        layers.del ? h('div', null,
          h('div', { style: laneHeadStyle }, '▲ ПОСТАВКИ · ' + delEv.length),
          h('div', { style: { position: 'relative', height: 34, background: 'var(--rd-surface)' } },
            delEv.map(function (e, i) { return h('div', { key: i, title: e.text + ' ' + e.sub, style: { position: 'absolute', left: labelW + sX(e.day), top: 8, transform: 'translateX(-50%)', padding: '2px 6px', background: e.color, color: '#fff', borderRadius: 5, fontSize: 9, fontWeight: 600, whiteSpace: 'nowrap' } }, '▲ ' + e.text); }),
            h('div', { style: { position: 'absolute', left: labelW + sX(todayD), top: 0, height: 34, width: 2, background: 'rgba(207,59,59,.3)' } }))) : null,
        // ДОРОЖКА: РАБОЧАЯ СИЛА (гистограмма)
        layers.labor ? h('div', null,
          h('div', { style: laneHeadStyle }, '▦ РАБОЧАЯ СИЛА · пик ' + laborPeak + ' чел'),
          h('div', { style: { position: 'relative', height: 50, background: 'var(--rd-surface)' } },
            laborDay.map(function (pd) { return pd.tot ? h('div', { key: 'l' + pd.d, title: 'д' + pd.d + ': ' + pd.tot + ' чел', style: { position: 'absolute', left: labelW + sX(pd.d), bottom: 0, width: Math.max(1, dayW - 1), height: Math.round(pd.tot / laborPeak * 44), background: pd.tot > TRADE_CAP ? 'var(--rd-bad)' : 'var(--rd-accent)', opacity: .8 } }) : null; }),
            h('div', { style: { position: 'absolute', left: labelW + sX(todayD), top: 0, height: 50, width: 2, background: 'rgba(207,59,59,.3)' } }))) : null,
        // ДОРОЖКА: ФИНАНСИРОВАНИЕ (накопит. cash-flow)
        layers.cash ? h('div', null,
          h('div', { style: laneHeadStyle }, '⌁ ФИНАНСИРОВАНИЕ · накопл. ' + moneyShort(cashTotal) + ' ₽'),
          h('div', { style: { position: 'relative', height: 50, background: 'var(--rd-surface)' } },
            h('svg', { style: { position: 'absolute', left: labelW, top: 0, width: gTotalW, height: 50, overflow: 'visible' } },
              h('path', { d: cashCum.map(function (v, d) { return (d === 0 ? 'M' : 'L') + ' ' + sX(d) + ' ' + (46 - v / cashTotal * 42); }).join(' '), style: { fill: 'none', stroke: 'var(--rd-accent)', strokeWidth: 2 } })),
            h('div', { style: { position: 'absolute', left: labelW + sX(todayD), top: 0, height: 50, width: 2, background: 'rgba(207,59,59,.3)' } }))) : null
      )
    ),
    sel.length ? h('div', { style: { position: 'fixed', bottom: 18, left: '50%', transform: 'translateX(-50%)', display: 'flex', alignItems: 'center', gap: 8, background: 'var(--rd-text)', color: 'var(--rd-surface)', padding: '9px 14px', borderRadius: 11, boxShadow: '0 8px 28px rgba(0,0,0,.3)', zIndex: 70 } },
      h('span', { style: { fontSize: 12, fontWeight: 600 } }, 'Выбрано: ' + sel.length),
      h('button', { onClick: function () { bulkShift(-1); }, style: { height: 28, padding: '0 10px', border: 'none', borderRadius: 7, background: 'rgba(255,255,255,.15)', color: '#fff', fontSize: 12, cursor: 'pointer' } }, '−1 день'),
      h('button', { onClick: function () { bulkShift(1); }, style: { height: 28, padding: '0 10px', border: 'none', borderRadius: 7, background: 'rgba(255,255,255,.15)', color: '#fff', fontSize: 12, cursor: 'pointer' } }, '+1 день'),
      h('button', { onClick: function () { bulkShift(3); }, style: { height: 28, padding: '0 10px', border: 'none', borderRadius: 7, background: 'rgba(255,255,255,.15)', color: '#fff', fontSize: 12, cursor: 'pointer' } }, '+3 дня'),
      h('button', { onClick: bulkBrigade, style: { height: 28, padding: '0 10px', border: 'none', borderRadius: 7, background: 'rgba(255,255,255,.15)', color: '#fff', fontSize: 12, cursor: 'pointer' } }, 'Бригада'),
      h('button', { onClick: clearSel, style: { height: 28, padding: '0 8px', border: 'none', background: 'none', color: 'rgba(255,255,255,.6)', fontSize: 12, cursor: 'pointer' } }, 'Снять')) : null,
    workDrawer ? (function () {
      var w = (store.raw.works || []).find(function (x) { return x.id === workDrawer; }); if (!w) return null;
      var brigades = store.raw.brigades || [], sections = store.raw.sections || [];
      var succ = (store.raw.works || []).filter(function (x) { return (x.deps || []).some(function (d) { return d.f === w.id; }); }).map(function (x) { return x.name; });
      return h('div', { onClick: function () { setWorkDrawer(null); }, style: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,.34)', zIndex: 60, display: 'flex', justifyContent: 'flex-end' } },
        h('div', { onClick: function (e) { e.stopPropagation(); }, style: { width: 380, maxWidth: '92vw', height: '100%', background: 'var(--rd-surface)', boxShadow: '-12px 0 40px rgba(0,0,0,.2)', overflow: 'auto' } },
          h('div', { style: { padding: 18, borderBottom: '1px solid var(--rd-border)', display: 'flex', alignItems: 'center', gap: 10 } },
            h('div', { style: { flex: 1 } }, h('div', { style: { fontSize: 15, fontWeight: 600 } }, w.name), h('div', { style: { fontSize: 11.5, color: 'var(--rd-text-muted)' } }, 'д' + w.start + '–д' + w.end + ' · ' + C.statusMeta(w.status).label + (crit[w.id] ? ' · крит. путь' : ''))),
            h('button', { onClick: function () { setWorkDrawer(null); }, style: { border: 'none', background: 'none', fontSize: 20, color: 'var(--rd-text-muted)', cursor: 'pointer' } }, '×')),
          h('div', { style: { padding: 16 } },
            h('div', { style: { fontSize: 10, fontWeight: 600, color: 'var(--rd-text-muted)', marginBottom: 7, letterSpacing: .4 } }, 'ЗАВИСИМОСТИ (ПРЕДШЕСТВЕННИКИ)'),
            h('div', { style: { marginBottom: 14 } }, (w.deps || []).length ? (w.deps || []).map(function (d) {
              var p = (store.raw.works || []).find(function (x) { return x.id === d.f; });
              return h('div', { key: d.f, style: { display: 'flex', alignItems: 'center', gap: 6, padding: '6px 0', borderBottom: '1px solid var(--rd-border)' } },
                h('span', { style: { flex: 1, fontSize: 11.5, color: 'var(--rd-text-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, p ? p.name : d.f),
                ['FS', 'SS', 'FF'].map(function (tt) { return h('button', { key: tt, onClick: function () { setDepType(w.id, d.f, tt); }, style: { padding: '3px 6px', border: 'none', borderRadius: 5, fontSize: 9.5, fontWeight: 700, cursor: 'pointer', background: d.t === tt ? 'var(--rd-accent)' : 'var(--rd-surface-3)', color: d.t === tt ? '#fff' : 'var(--rd-text-2)' } }, tt); }),
                h('button', { onClick: function () { setDepLag(w.id, d.f, (d.lag || 0) - 1); }, style: { width: 20, height: 20, border: '1px solid var(--rd-border)', borderRadius: 5, background: 'var(--rd-surface)', cursor: 'pointer', fontSize: 12 } }, '−'),
                h('span', { className: 'mono', style: { fontSize: 11, minWidth: 26, textAlign: 'center' } }, (d.lag > 0 ? '+' : '') + (d.lag || 0) + 'д'),
                h('button', { onClick: function () { setDepLag(w.id, d.f, (d.lag || 0) + 1); }, style: { width: 20, height: 20, border: '1px solid var(--rd-border)', borderRadius: 5, background: 'var(--rd-surface)', cursor: 'pointer', fontSize: 12 } }, '+'),
                h('button', { onClick: function () { removeDep(w.id, d.f); }, title: 'Удалить связь', style: { width: 20, height: 20, border: 'none', borderRadius: 5, background: 'var(--rd-bad-soft,#fbeaea)', color: 'var(--rd-bad)', cursor: 'pointer', fontSize: 12 } }, '×'));
            }) : h('span', { style: { fontSize: 12, color: 'var(--rd-text-2)' } }, 'старт проекта')),
            h('div', { style: { fontSize: 10, fontWeight: 600, color: 'var(--rd-text-muted)', marginBottom: 7, letterSpacing: .4 } }, 'ПРЕЕМНИКИ'),
            h('div', { style: { fontSize: 12, color: 'var(--rd-text-2)', marginBottom: 14 } }, succ.length ? succ.join(' · ') : '—'),
            w.trade ? h('div', null, h('div', { style: { fontSize: 10, fontWeight: 600, color: 'var(--rd-text-muted)', marginBottom: 7, letterSpacing: .4 } }, 'БРИГАДА / СПЕЦИАЛЬНОСТЬ'), h('div', { style: { fontSize: 12, color: 'var(--rd-text-2)', marginBottom: 14 } }, w.trade + (w.crew ? ' · ' + w.crew + ' чел' : ''))) : null,
            h('button', { onClick: function () { toggleLock(w.id); }, style: { width: '100%', height: 36, border: '1px solid ' + (locked.indexOf(w.id) >= 0 ? 'var(--rd-accent)' : 'var(--rd-border)'), borderRadius: 8, background: locked.indexOf(w.id) >= 0 ? 'var(--rd-accent-soft)' : 'var(--rd-surface)', color: locked.indexOf(w.id) >= 0 ? 'var(--rd-accent)' : 'var(--rd-text-2)', fontSize: 12.5, fontWeight: 500, cursor: 'pointer' } }, locked.indexOf(w.id) >= 0 ? '🔒 Дата зафиксирована — разблокировать' : '🔓 Зафиксировать дату (не двигать каскадом)'),
            w.excl ? (function () { var alt = (store.raw.works || []).find(function (x) { return x.id === w.excl; }); return h('div', { style: { marginTop: 12, padding: '11px 12px', border: '1px dashed var(--rd-bad)', borderRadius: 8, background: 'var(--rd-bad-soft,#fbeaea)' } },
              h('div', { style: { fontSize: 10, fontWeight: 700, color: 'var(--rd-bad)', marginBottom: 5, letterSpacing: .4 } }, 'XOR · ВЗАИМОИСКЛЮЧЕНИЕ — нужен один вариант'),
              h('div', { style: { fontSize: 11.5, color: 'var(--rd-text-2)', lineHeight: 1.4, marginBottom: 9 } }, w.rejected ? ('Сейчас выбран «' + (alt ? alt.name : '') + '». Сравните и выберите этот вместо него?') : ('Альтернатива: «' + (alt ? alt.name : '') + '». Выбран этот вариант.')),
              (function () {
                function attrs(wk) {
                  if (wk && wk.compare) { var cp = wk.compare; return { cost: (typeof cp.cost === 'number' ? '~' + C.money0(cp.cost) + ' ₽' : (cp.cost || '—')), dur: cp.dur || '—', pros: cp.pros || '—', cons: cp.cons || '—' }; }
                  return { cost: '—', dur: '—', pros: 'нет данных сравнения в БД', cons: '—' };
                }
                var aThis = attrs(w), aAlt = attrs(alt);
                var cell = { padding: '5px 7px', fontSize: 10.5, lineHeight: 1.3, verticalAlign: 'top', borderTop: '1px solid var(--rd-border)' };
                var hd = { padding: '5px 7px', fontSize: 10, fontWeight: 700, color: 'var(--rd-text-muted)', textAlign: 'left' };
                return h('table', { style: { width: '100%', borderCollapse: 'collapse', marginBottom: 10, background: 'var(--rd-surface)', borderRadius: 6 } },
                  h('thead', null, h('tr', null,
                    h('th', { style: hd }, ''),
                    h('th', { style: Object.assign({}, hd, { color: 'var(--rd-accent)' }) }, '▸ ' + w.name.replace(/Тёплый пол:\s*/i, '')),
                    h('th', { style: hd }, alt ? alt.name.replace(/Тёплый пол:\s*/i, '') : '—'))),
                  h('tbody', null,
                    [['Срок', aThis.dur, aAlt.dur], ['Стоимость', aThis.cost, aAlt.cost], ['Плюсы', aThis.pros, aAlt.pros], ['Минусы', aThis.cons, aAlt.cons],
                     ['План', 'д' + w.start + '–д' + w.end, alt ? ('д' + alt.start + '–д' + alt.end) : '—']].map(function (r, i) {
                      return h('tr', { key: i },
                        h('td', { style: Object.assign({}, cell, { fontWeight: 600, color: 'var(--rd-text-muted)', whiteSpace: 'nowrap' }) }, r[0]),
                        h('td', { style: Object.assign({}, cell, { color: 'var(--rd-text)' }) }, r[1]),
                        h('td', { style: Object.assign({}, cell, { color: 'var(--rd-text-2)' }) }, r[2]));
                    })));
              })(),
              h('button', { onClick: function () { chooseXor(w.id, w.excl); setWorkDrawer(null); }, style: { width: '100%', height: 32, border: 'none', borderRadius: 7, background: 'var(--rd-accent)', color: '#fff', fontSize: 12, fontWeight: 600, cursor: 'pointer', marginBottom: 6 } }, w.rejected ? '✓ Выбрать этот (' + w.name.replace(/Тёплый пол:\s*/i, '') + ')' : '✓ Оставить этот (' + w.name.replace(/Тёплый пол:\s*/i, '') + ')'),
              alt ? h('button', { onClick: function () { chooseXor(w.excl, w.id); setWorkDrawer(null); }, style: { width: '100%', height: 32, border: '1px solid var(--rd-accent)', borderRadius: 7, background: 'transparent', color: 'var(--rd-accent)', fontSize: 12, fontWeight: 600, cursor: 'pointer' } }, '⇄ Выбрать «' + alt.name.replace(/Тёплый пол:\s*/i, '') + '» вместо этого') : null); })() : null
          )
        )
      );
    })() : null
  );
};

