/* procurement-v2-app.jsx — кабинет закупок в виде кабинета клиента (KSH-456): шапка, главная со сводкой, разделы — окнами по #hash.
   Собран из библиотеки виджетов lk-widgets.jsx (window.LKW) на данных procurement-views.js (window.ProcViews).
   Главная: сводка (срыв этапа, к заказу, заявки на доставку, в пути, возвраты), «Сейчас важно», заказы поставщикам, заявки на доставку,
   чаты, объекты, «Мои задачи» (общий таск-менеджер: те же строки, что в прежних «Мои задачи» закупок, плюс задачи и решения роли).
   Окна: #queue[=red|amber|appr|<pid>] — очередь закупок: выбрать позиции и создать заказы по поставщикам (ProjectStore.createOrder);
   #orders[=<вкладка>] — заявки на доставку, заказы, в пути, принятые; #dl=<pid>~<id> — заявка: оформить заказ (orderDelivery), своя дата
   (setDeliveryDate), ответственный (setDeliveryPeople); #po=<pid>~<id> — заказ: состав, приёмка, план/факт, «Отгружен» (setOrderStatus),
   возвраты (setReturnStatus); #accept=<pid>~<id> — приёмка поставки (LKW.DeliveryAccept — та же, что у прораба: acceptDelivery, createReturn);
   #objects, #obj=<pid> — комплектация по этапам и бюджет закупок; #suppliers, #sup=<имя>; #rpl, #rplnew[=<pid>~<matId>] — замены
   (createReplacement); #chat[=project:<pid>:<tid>]; #feed — лента материалов. Документы и профиль — прежние страницы в «Ещё». */
(function () {
  'use strict';
  const PV = window.ProcViews, W = window.LKW, A = window.AccessStore;
  const P = window.ProjectStore;
  const { Ic, Header, Modal, FactsBand, NowCards, RowsW, ChatsW, rub, plural, cap, dateR, short } = W;
  const t0 = () => new Date().toISOString().slice(0, 10);

  function useMe() {
    const acc = A && A.currentAccount ? A.currentAccount() : null;
    const ac = W.access(acc, ['procurement', 'super']);
    const home = ac.zone('dash'), orders = ac.zone('orders'), rpl = ac.zone('replacements'), objs = ac.zone('completeness'), sups = ac.zone('suppliers'), chat = ac.zone('chat');
    return { acc, name: (acc && acc.name) || 'Закупки', home, orders, rpl, objs, sups, chat,
      deny: ac.deny || (home || orders || rpl || objs || sups || chat ? '' : 'Кабинет закупок не входит в выданный доступ.') };
  }
  const actor = (me) => ({ role: 'procurement', name: me.name });
  const EVS = ['storage', 'obitel:sync'];
  function useStore() {
    const [, tick] = React.useState(0);
    React.useEffect(() => (P ? P.subscribe(() => tick((x) => x + 1)) : undefined), []);
    React.useEffect(() => { const on = () => tick((x) => x + 1); EVS.forEach((e) => window.addEventListener(e, on)); return () => EVS.forEach((e) => window.removeEventListener(e, on)); }, []);
  }
  // старые адреса «Сегодня → Очередь / Лента», «Заказы → Поставки / Доставки» (?tab=) открывают своё окно
  const TAB_HASH = { queue: 'queue', feed: 'feed', deliveries: 'orders=transit', 'site-deliveries': 'orders=dl' };
  function useHash() {
    const read = () => decodeURIComponent((window.location.hash || '').replace('#', ''));
    const [m, setM] = React.useState(() => { const h = read(); const tb = new URLSearchParams(window.location.search).get('tab'); return (!h || h === 'orders') && tb && TAB_HASH[tb] ? TAB_HASH[tb] : h; });
    React.useEffect(() => { const on = () => setM(read()); window.addEventListener('hashchange', on); return () => window.removeEventListener('hashchange', on); }, []);
    const open = (id) => { window.location.hash = id; };
    const close = () => { history.replaceState(null, '', window.location.pathname + window.location.search); setM(''); };
    return [m, open, close];
  }
  const greeting = () => { const h = new Date().getHours(); return h < 5 ? 'Доброй ночи' : h < 12 ? 'Доброе утро' : h < 18 ? 'Добрый день' : 'Добрый вечер'; };
  const todayLine = () => cap(new Date().toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' }));
  const when = (iso) => { if (!iso) return ''; const d = String(iso).slice(0, 10); return d === t0() ? new Date(iso).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }) : dateR(d, { month: 'short' }); };
  const dr = (d) => (d ? dateR(String(d).slice(0, 10), { month: 'short' }) : '');
  const go = (url) => { window.location.href = url; };
  const keep = (page) => page + window.location.search;

  const NAV = [['', 'Главная'], ['orders', 'Заказы'], ['objects', 'Объекты'], ['suppliers', 'Поставщики'], ['chat', 'Чаты']];
  const MORE = [['rpl', 'Замены и возвраты'], ['feed', 'Лента'], ['tools.html?role=procurement', 'Инструменты'], ['procurement-documents.html', 'Документы'], ['procurement-profile.html', 'Мой профиль']];
  const RISK_CLS = { red: 'pill-n cv-over', amber: 'pill-n', green: 'pill-g' };
  const PO_CLS = { ordered: 'pill-n', in_transit: 'pill-n', partially_delivered: 'pill-n', arrived: 'pill-g', cancelled: 'pill-d' };
  const err = (e) => (e ? <div className="att" role="alert" style={{ marginTop: 12 }}><Ic n="alert" s={16} /><span>{e}</span></div> : null);
  function useRun() {
    const [e, setE] = React.useState(''); const [ok, setOk] = React.useState('');
    const run = (fn, msg) => { try { fn(); setE(''); setOk(msg || ''); return true; } catch (x) { setE(x.message); setOk(''); return false; } };
    return [run, e, ok];
  }

  /* «Мои задачи»: строки закупок (ProcViews.tasks) + задачи и решения роли из общего таск-менеджера */
  function myInbox(d, open) {
    const T = window.TaskViews;
    const order = (r) => (r.late ? 0 : r.now ? 1 : 2);
    const own = PV.tasks(d).map((r) => Object.assign({}, r, { act: () => open(r.hash) }));
    const dec = T && T.decisionRows ? T.decisionRows('procurement').map((r) => Object.assign({}, r, { act: () => open('rpl') })) : [];
    const inbox = T && T.inbox ? T.inbox('procurement', 'procurement', t0()) : [];
    return own.concat(dec, inbox).sort((a, b) => order(a) - order(b) || String(a.due || '9999').localeCompare(String(b.due || '9999')));
  }

  /* ───── главная ───── */
  function Home({ me, d, open }) {
    const { f } = d;
    const T = window.TaskViews;
    const first = me.acc && me.acc.name ? me.acc.name.split(' ')[0] : '';
    const tasks = PV.tasks(d);
    const now = tasks.filter((r) => r.late || r.now).slice(0, 3).map((r) => ({ key: r.id, kindL: r.kindL, late: !!r.late, title: r.title, sub: [r.obj ? short(r.obj) : '', r.sub].filter(Boolean).join(' · '),
      act: { text: r.next.l, main: r.next.who === 'me', onClick: () => open(r.hash) } }));
    const live = d.orders.filter((o) => o.status !== 'arrived' && o.status !== 'cancelled');
    const inbox = myInbox(d, open);
    return (
      <div className="cv-page lw-page">
        <Header nav={NAV} more={MORE} cur="" right={<span className="cv-ava" title={me.name}>{me.name.split(' ').map((w) => w[0]).join('').slice(0, 2)}</span>} />
        <main id="main" tabIndex={-1} className="cv-main">
          <section className="cv-hero">
            <div><div className="lab">{todayLine()} · {d.objs.length} {plural(d.objs.length, 'объект', 'объекта', 'объектов')} в работе</div><h1 className="cv-hello">{greeting()}{first ? ', ' + first : ''}</h1></div>
            <div className="cv-hero-act">
              {f.toOrder ? <button type="button" className="btn-k" onClick={() => open('queue')}>К заказу · {f.toOrder}</button>
                : <span className="pill-g"><span className="dot" />Заказы идут по графику</span>}
            </div>
          </section>

          <FactsBand label="Сводка закупок"
            live={<span className="now">{f.red ? <React.Fragment><i className="cv-pulse" aria-hidden="true" /><span>{f.red} поз. сорвут этап{f.factLate ? ' · ' + f.factLate + ' — этап уже идёт' : ''}</span></React.Fragment> : <span>Срывов этапов нет</span>}</span>}
            facts={[
              { v: f.red + ' ' + plural(f.red, 'позиция', 'позиции', 'позиций'), lab: 'Срыв этапа · заказать сегодня', onClick: () => open('queue=red'), late: f.red > 0 },
              { v: f.amber + ' на неделе', lab: 'Пора заказать', onClick: () => open('queue=amber') },
              { v: f.requested + ' ' + plural(f.requested, 'заявка', 'заявки', 'заявок'), lab: 'Доставки оформить', onClick: () => open('orders=dl'), late: d.requests.urgent.some((r) => r.overdue) },
              { v: f.transit + ' в пути', lab: 'Принять на объекте', onClick: () => open('orders=transit') },
              { v: f.retOpen + ' ' + plural(f.retOpen, 'возврат', 'возврата', 'возвратов'), lab: f.rplPending ? 'и ' + f.rplPending + ' замен на согласовании' : 'Открытые возвраты', onClick: () => open('rpl') },
            ]} />

          <NowCards rows={now} />

          <section className="cv-two lw-two">
            {me.orders ? <RowsW id="pz-po-h" title="Заказы поставщикам" n={live.length} className="is-grow" text={'Все' + (d.orders.length > 8 ? ' · ' + d.orders.length : '')} onOpen={() => open('orders=po')}
              rows={live.slice(0, 8).map((o) => ({ key: o.pid + o.id, ic: o.status === 'in_transit' ? 'truck' : 'doc', late: o.late, title: o.id + ' · ' + o.supplier, sub: short(o.obj) + ' · ' + o.lineCount + ' поз. · ' + rub(o.fact), right: o.statusL, rightLab: 'ETA ' + o.eta, onClick: () => open('po=' + o.pid + '~' + o.id) }))}
              empty="Открытых заказов нет — создайте их в очереди закупок." /> : <div />}
            <div className="cv-side">
              {me.orders && <RowsW id="pz-dl-h" title="Заявки на доставку" n={f.requested} max={300} text="Все" onOpen={() => open('orders=dl')}
                rows={d.requests.urgent.slice(0, 4).map((r) => ({ key: r.pid + r.id, ic: 'truck', late: r.overdue, title: r.stage + ' · ' + short(r.obj), sub: 'к ' + r.needByR + ' · ' + r.itemsCount + ' поз. · ' + r.resp, right: r.statusL, onClick: () => open('dl=' + r.pid + '~' + r.id) }))}
                empty="Срочных заявок нет — следующие появятся по графику этапов." />}
              {me.chat && <ChatsW chats={d.chats.map((c) => Object.assign({}, c, { when: when(c.at) }))} onOpen={() => open('chat')} onPick={(c) => open('chat=' + c.src + ':' + c.pid + ':' + c.id)} />}
            </div>
          </section>

          {me.objs && d.objs.length > 0 && <section className="lw-sec" aria-labelledby="pz-obj-h">
            <div className="lw-sech"><h2 className="h2" id="pz-obj-h">Комплектация объектов</h2><button type="button" className="cv-arrow-btn" onClick={() => open('objects')}>Все объекты<Ic n="ne" s={14} /></button></div>
            <div className="lw-tiles">{d.objs.slice(0, 4).map((o) => <ObjTile key={o.id} o={o} onOpen={() => open('obj=' + o.id)} />)}</div>
          </section>}

          {T && T.inbox && <section id="pz-tasks" className="cv-w lw-gw lw-scope" aria-label="Мои задачи">
            <W.TaskList title="Мои задачи" t0={t0()} onOpen={(r) => (r.act ? r.act() : r.hash ? open(r.hash) : r.href ? go(r.href) : null)}
              tabs={[
                { id: 'in', label: 'Мне сделать', rows: inbox, empty: 'Ничего не горит: заказы идут по графику.', boardRows: inbox.concat(T.inboxDone ? T.inboxDone('procurement', t0()) : []), columns: T.IN_COLUMNS, colOf: T.inCol },
              ].concat(T.outbox ? [{ id: 'out', label: 'Отправлено мной', rows: T.outbox({ role: 'procurement' }, t0()), empty: 'Вы ничего не возвращали на доработку.',
                buckets: T.OUT_BUCKETS, bucketOf: T.outBucket, boardRows: T.outbox({ role: 'procurement' }, t0(), { closed: true }), columns: T.OUT_COLUMNS, colOf: T.outCol }] : [])} />
          </section>}
        </main>
      </div>);
  }

  function ObjTile({ o, onOpen }) {
    return (
      <button type="button" className="lw-tile" onClick={onOpen} aria-label={'Открыть объект ' + o.title}>
        <span className="lw-tph">{o.cover ? <img src={o.cover} alt="" loading="lazy" /> : <span className="ph0"><Ic n="key" s={28} /></span>}
          {o.red ? <span className="fl"><span>{o.red} сорвут этап</span></span> : o.toOrder ? <span className="fl"><span>{o.toOrder} к заказу</span></span> : null}
          <span className="cv-wcirc lw-tcirc" aria-hidden="true"><Ic n="ne" s={18} /></span></span>
        <span className="tx">
          <span className="t">{o.title}</span>
          <span className="lab">{[o.client, o.id].filter(Boolean).join(' · ')}</span>
          <span className="mg-bar pz-bar"><i style={{ width: o.pct + '%' }} /></span>
          <span className="row"><span className="st">{o.stage}</span><span className="pc">{o.pct} %</span></span>
          <span className="lab">материалов на объекте · {o.onsite} из {o.n}</span>
        </span>
      </button>);
  }

  /* ───── очередь закупок: выбрать позиции → заказы по поставщикам ───── */
  const Q_TABS = [['all', 'Все'], ['red', 'Срыв этапа'], ['amber', 'Пора заказать'], ['appr', 'Ждут согласования'], ['ordered', 'Заказано и в пути']];
  function QueueModal({ me, d, sel, open, close }) {
    const pidSel = sel && !Q_TABS.some(([k]) => k === sel) ? sel : '';
    const [tab, setTab] = React.useState(Q_TABS.some(([k]) => k === sel) ? sel : 'all');
    const [obj, setObj] = React.useState(pidSel || 'all');
    const [pick, setPick] = React.useState(() => new Set());
    const [sup, setSup] = React.useState('');
    const [run, e, ok] = useRun();
    const live = d.lines.filter((l) => l.status !== 'on_site' && l.status !== 'installed');
    const of = (k) => live.filter((l) => (obj === 'all' || l.pid === obj) && (k === 'all' ? true : k === 'red' ? l.status === 'planned' && l.risk === 'red' : k === 'amber' ? l.status === 'planned' && l.risk === 'amber'
      : k === 'appr' ? l.status === 'planned' && l.approval === 'pending' : l.status !== 'planned'));
    // сначала то, что сорвёт этап, потом «пора заказать», затем по дате заказа
    const RK = (l) => (l.status !== 'planned' ? 3 : l.risk === 'red' ? 0 : l.risk === 'amber' ? 1 : 2);
    const rows = of(tab).slice().sort((a, b) => RK(a) - RK(b) || String(a.latestIso).localeCompare(String(b.latestIso)));
    const key = (l) => l.pid + '~' + l.id;
    const toggle = (l) => setPick((s) => { const n = new Set(s); n.has(key(l)) ? n.delete(key(l)) : n.add(key(l)); return n; });
    const chosen = live.filter((l) => pick.has(key(l)));
    const byPid = chosen.reduce((a, l) => { (a[l.pid] = a[l.pid] || []).push(l.id); return a; }, {});
    const create = () => {
      const name = sup.trim() || 'Поставщик';
      if (run(() => Object.keys(byPid).forEach((pid) => P.createOrder(pid, byPid[pid], name, actor(me))), 'Создано заказов: ' + Object.keys(byPid).length + ' · ' + name)) { setPick(new Set()); setSup(''); }
    };
    const objs = d.objs.filter((o) => live.some((l) => l.pid === o.id));
    return (
      <Modal size="wide" title="Очередь закупок" sub={live.length + ' ' + plural(live.length, 'позиция', 'позиции', 'позиций') + ' ещё не на объекте'} onClose={close}>
        <div className="mg-tabs"><W.CalTabs kinds={Q_TABS} tab={tab} setTab={setTab} counts={Q_TABS.reduce((a, [k]) => Object.assign(a, { [k]: of(k).length }), {})} label="Какие позиции показать" /></div>
        {objs.length > 1 && <label className="pz-sel"><span className="lab">Объект</span><select value={obj} onChange={(x) => setObj(x.target.value)}><option value="all">Все объекты</option>{objs.map((o) => <option key={o.id} value={o.id}>{short(o.title)}</option>)}</select></label>}
        {err(e)}{ok ? <div className="note-s" style={{ marginTop: 12 }}><Ic n="check" c="#2F5D3A" s={16} /><span>{ok}</span></div> : null}
        {rows.length ? <div className="pz-q">{rows.map((l) => {
          const can = l.status === 'planned' || l.status === 'delayed'; const on = pick.has(key(l));
          return (
            <div key={key(l)} className={'pz-qr' + (on ? ' on' : '')}>
              {can ? <input type="checkbox" checked={on} onChange={() => toggle(l)} aria-label={'Выбрать ' + l.name} /> : <span className="pz-qx" aria-hidden="true" />}
              <span className="tx"><span className="t">{l.name}</span><span className="lab">{[short(l.obj), 'этап «' + l.stage + '»', l.plan + ' ' + l.unit, rub(l.purchase)].join(' · ')}</span>
                <span className="lab">нужно к {l.reqOn} · {l.factLate ? <b className="mg-late">этап уже идёт — не заказано</b> : 'заказать до ' + l.latest}{l.approval === 'pending' ? ' · ждёт согласования' : ''}{l.po ? ' · ' + l.po + (l.supplier !== '—' ? ' · ' + l.supplier : '') : ''}</span></span>
              <span className="pz-qs"><span className={l.status === 'planned' ? RISK_CLS[l.risk] : 'pill-d'}>{l.status === 'planned' ? PV.RISK_L[l.risk] : PV.PROC_L[l.status] || l.status}</span>
                {me.rpl && <button type="button" className="lw-btn-l" onClick={() => open('rplnew=' + key(l))}>Замена</button>}</span>
            </div>); })}</div> : <div className="note-s" style={{ marginTop: 18 }}><span>В этом фильтре позиций нет.</span></div>}
        {chosen.length > 0 && <div className="pz-bat" role="region" aria-label="Создать заказ">
          <span className="t">{chosen.length} {plural(chosen.length, 'позиция', 'позиции', 'позиций')} · {rub(chosen.reduce((a, l) => a + (+l.purchase || 0), 0))}{Object.keys(byPid).length > 1 ? ' · ' + Object.keys(byPid).length + ' объекта — по заказу на каждый' : ''}</span>
          <SupplierInput d={d} value={sup} onChange={setSup} />
          <button type="button" className="lw-btn-l" onClick={() => setPick(new Set())}>Сбросить</button>
          <button type="button" className="lw-btn-k" onClick={create}>Создать заказ</button>
        </div>}
      </Modal>);
  }
  // поставщик — из уже знакомых (по заказам) или новый: поле с подсказками вместо prompt
  function SupplierInput({ d, value, onChange }) {
    return <React.Fragment><input className="pz-in" list="pz-sups" value={value} onChange={(e) => onChange(e.target.value)} placeholder="Поставщик" aria-label="Поставщик" />
      <datalist id="pz-sups">{d.sups.map((s) => <option key={s.name} value={s.name} />)}</datalist></React.Fragment>;
  }

  /* ───── заказы и доставки ───── */
  const O_TABS = [['dl', 'Заявки на доставку'], ['po', 'Заказы'], ['transit', 'В пути'], ['done', 'Принятые']];
  function OrdersModal({ d, sel, open, close }) {
    const [tab, setTab] = React.useState(O_TABS.some(([k]) => k === sel) ? sel : 'po');
    const [more, setMore] = React.useState(false);
    const of = (k) => (k === 'dl' ? d.requests.urgent : k === 'po' ? d.orders.filter((o) => o.status === 'ordered' || o.status === 'partially_delivered') : k === 'transit' ? d.orders.filter((o) => o.status === 'in_transit') : d.orders.filter((o) => o.status === 'arrived'));
    const poRow = (o) => (
      <button type="button" key={o.pid + o.id} className="mg-deal" onClick={() => open('po=' + o.pid + '~' + o.id)}>
        <span className="t">{o.id} · {o.supplier}</span><span className="lab">{short(o.obj)} · {o.names.slice(0, 2).join(', ')}{o.names.length > 2 ? ' +' + (o.names.length - 2) : ''}</span>
        <span className="r"><b className={o.late ? 'mg-late' : ''}>{rub(o.fact)}</b><span className="lab">{o.statusL} · ETA {o.eta}</span></span>
      </button>);
    const dlRow = (r) => (
      <button type="button" key={r.pid + r.id} className="mg-deal" onClick={() => open('dl=' + r.pid + '~' + r.id)}>
        <span className="t">{r.stage} · {short(r.obj)}</span><span className="lab">привезти к {r.needByR} · {r.itemsCount} поз. · {r.custom ? 'своя дата' : 'по графику'} · {r.resp}</span>
        <span className="r"><b className={r.overdue ? 'mg-late' : ''}>{r.statusL}</b><span className="lab">{rub(r.sum)}</span></span>
      </button>);
    const list = of(tab);
    return (
      <Modal size="wide" title="Заказы и доставки" sub={d.orders.length + ' ' + plural(d.orders.length, 'заказ', 'заказа', 'заказов') + ' · ' + d.requests.all.length + ' ' + plural(d.requests.all.length, 'заявка', 'заявки', 'заявок') + ' на доставку'} onClose={close}>
        <div className="mg-tabs"><W.CalTabs kinds={O_TABS} tab={tab} setTab={setTab} counts={O_TABS.reduce((a, [k]) => Object.assign(a, { [k]: of(k).length }), {})} label="Что показать" /></div>
        {list.length ? <div className="mg-list">{list.map((x) => (tab === 'dl' ? dlRow(x) : poRow(x)))}</div>
          : <div className="note-s" style={{ marginTop: 18 }}><span>{tab === 'dl' ? 'Срочных заявок нет.' : tab === 'transit' ? 'Отгруженных заказов нет — отметьте «Отгружен» в заказе.' : 'Здесь пока пусто.'}</span></div>}
        {tab === 'dl' && d.requests.planned.length > 0 && <section className="dz-sec"><h3 className="t">Запланированы · {d.requests.planned.length}</h3>
          <div className="lab">Появятся в «Оформить» за несколько дней до этапа — по графику или по ходу работ.</div>
          <div className="mg-list">{(more ? d.requests.planned : d.requests.planned.slice(0, 5)).map(dlRow)}</div>
          {d.requests.planned.length > 5 && <button type="button" className="lw-btn-l" style={{ marginTop: 10 }} onClick={() => setMore(!more)}>{more ? 'Свернуть' : 'Показать все'}</button>}</section>}
      </Modal>);
  }

  function DlModal({ arg, me, d, open, close }) {
    const [pid, id] = arg.split('~');
    const r = d.requests.all.find((x) => x.pid === pid && x.id === id);
    const [sup, setSup] = React.useState('');
    const [date, setDate] = React.useState('');
    const [run, e, ok] = useRun();
    if (!r) return <Modal title="Заявка не найдена" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Возможно, этап перенесли или проект закрыт.</div></Modal>;
    const p = P.get(pid) || { materials: [] };
    const items = r.items.map((it) => p.materials.find((m) => m.id === it.materialId)).filter(Boolean);
    const S = window.StaffStore; const people = S && S.forRole ? S.forRole('procurement') : [];
    const canOrder = r.status === 'requested' || r.status === 'planned';
    return (
      <Modal size="wide" title={'Доставка · ' + r.stage} sub={[r.obj, 'привезти к ' + r.needByR, r.custom ? 'своя дата' : 'по графику'].join(' · ')} onClose={close}
        head={<span className={r.overdue ? 'pill-n cv-over' : 'pill-n'}>{r.statusL}</span>}>
        {err(e)}{ok ? <div className="note-s" style={{ marginTop: 12 }}><Ic n="check" c="#2F5D3A" s={16} /><span>{ok}</span></div> : null}
        <section className="dz-sec"><h3 className="t">Позиции · {items.length}</h3>
          <div className="mg-rows">{items.map((m) => (
            <div key={m.id} className="r"><span className="tx"><span className="t">{m.name}</span><span className="lab">{[m.qty + ' ' + m.unit, PV.PROC_L[m.proc] || m.proc, m.approval && m.approvalState !== 'approved' ? 'ждёт согласования' : ''].filter(Boolean).join(' · ')}</span></span>
              <span className="lw-racts"><b>{rub(m.price)}</b>{canOrder && items.length > 1 && <button type="button" className="lw-btn-l" title="Другой поставщик или другая дата — своя заявка" onClick={() => run(() => P.splitDelivery(pid, id, m.id, actor(me)), '«' + m.name + '» — в отдельной заявке')}>В отдельную заявку</button>}</span></div>))}</div>
        </section>
        {r.status !== 'delivered' && <SlotForm r={r} me={me} />}
        {canOrder ? <section className="dz-sec"><h3 className="t">Оформить заказ</h3>
          <div className="pz-row"><SupplierInput d={d} value={sup} onChange={setSup} /><button type="button" className="lw-btn-k" onClick={() => run(() => P.orderDelivery(pid, id, sup.trim() || 'Поставщик', actor(me)), 'Заказ оформлен')}>Оформить заказ</button></div>
        </section> : r.orderIds.length > 0 && <section className="dz-sec"><h3 className="t">Заказы</h3><div className="dz-acts">{r.orderIds.map((x) => <button key={x} type="button" className="lw-btn-l" onClick={() => open('po=' + pid + '~' + x)}>{x}</button>)}</div></section>}
        <section className="dz-sec"><h3 className="t">Дата и ответственный</h3>
          <div className="lab">{r.custom ? 'Своя дата заменила плановую.' : r.trigger && r.trigger.kind === 'progress' ? 'Заявка открылась раньше плана — по ходу работ.' : 'Дата — по старту этапа в графике.'} Принимает на объекте: {PV.staff(r.receiverId) || 'прораб'}.</div>
          <div className="pz-row"><input className="pz-in" type="date" value={date} onChange={(x) => setDate(x.target.value)} aria-label="Своя дата" />
            <button type="button" className="lw-btn-l" disabled={!date} onClick={() => run(() => P.setDeliveryDate(pid, id, date, actor(me)), 'Дата изменена')}>Поставить свою дату</button></div>
          {people.length > 0 && <label className="pz-sel"><span className="lab">Ответственный</span><select value={r.responsibleId || ''} onChange={(x) => run(() => P.setDeliveryPeople(pid, id, { responsibleId: x.target.value || null }, actor(me)), 'Ответственный назначен')}>
            <option value="">не назначен</option>{people.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}</select></label>}
        </section>
      </Modal>);
  }

  /* анкета доставки для клиента (окно приезда, кто везёт, машина, пропуск) — те же setDeliverySlot / sendDeliverySlot, что в прежней карточке доставки */
  const SLOT_ST = { draft: 'Черновик', sent: 'Ждёт ответа клиента', confirmed: 'Клиент подтвердил', reschedule: 'Клиент просит другое время' };
  const SLOT_ACC = { none: 'не нужен', pass: 'нужен пропуск', key: 'нужен ключ', pass_key: 'пропуск и ключ' };
  const winR = (x) => (x.date ? dr(x.date) : '—') + (x.from ? ', ' + x.from + (x.to ? '–' + x.to : '') : '');
  function SlotForm({ r, me }) {
    const sl = r.raw.slot || null;
    const blank = { date: r.needBy, from: '10:00', to: '13:00', carrier: { kind: 'supplier', name: '', phone: '' }, cargo: '', vehicle: { make: '', plate: '' }, volume: '', access: 'none' };
    const [edit, setEdit] = React.useState(false);
    const [f, setF] = React.useState(() => Object.assign({}, blank, sl || {}, { carrier: Object.assign({}, blank.carrier, sl && sl.carrier), vehicle: Object.assign({}, blank.vehicle, sl && sl.vehicle) }));
    const [run, e] = useRun();
    const set = (k, sub) => (x) => setF(sub ? Object.assign({}, f, { [k]: Object.assign({}, f[k], { [sub]: x.target.value }) }) : Object.assign({}, f, { [k]: x.target.value }));
    const save = (send) => { if (run(() => { P.setDeliverySlot(r.pid, r.id, f, actor(me)); if (send) P.sendDeliverySlot(r.pid, r.id, actor(me)); })) setEdit(false); };
    const c = (sl && sl.client) || {};
    return (
      <section className="dz-sec"><h3 className="t">Анкета для клиента{sl ? ' · ' + (SLOT_ST[sl.state] || sl.state) : ''}</h3>
        {err(e)}
        {!edit && (sl ? <div className="lab">Окно: {winR(sl)} · машина: {[sl.vehicle && sl.vehicle.make, sl.vehicle && sl.vehicle.plate].filter(Boolean).join(', ') || '—'} · пропуск: {SLOT_ACC[sl.access] || '—'}
          {sl.state === 'confirmed' ? ' · клиент: ' + ([c.passNumber ? 'пропуск № ' + c.passNumber : c.passOrdered ? 'пропуск заказан' : '', c.keyWhere ? 'ключ: ' + c.keyWhere : '', c.meetBy ? 'встречает: ' + c.meetBy : '', c.note].filter(Boolean).join(' · ') || 'подтвердил') : ''}
          {sl.state === 'reschedule' && c.proposed ? <b className="mg-late"> · клиент предлагает {winR(c.proposed)}{c.note ? ' «' + c.note + '»' : ''} — измените окно и отправьте заново</b> : null}</div>
          : <div className="lab">Окно приезда, кто везёт и пропуск — клиент подтвердит у себя в кабинете.</div>)}
        {!edit && <div className="dz-acts">{sl && sl.state === 'draft' && <button type="button" className="lw-btn-k" onClick={() => run(() => P.sendDeliverySlot(r.pid, r.id, actor(me)))}>Отправить клиенту</button>}
          <button type="button" className="lw-btn-l" onClick={() => setEdit(true)}>{sl ? 'Изменить' : 'Заполнить'}</button></div>}
        {edit && <div className="pz-slot">
          <label><span className="lab">Дата</span><input className="pz-in" type="date" value={f.date} onChange={set('date')} /></label>
          <label><span className="lab">С</span><input className="pz-in" type="time" value={f.from} onChange={set('from')} /></label>
          <label><span className="lab">До</span><input className="pz-in" type="time" value={f.to} onChange={set('to')} /></label>
          <label><span className="lab">Пропуск / ключ</span><select className="pz-in" value={f.access} onChange={set('access')}>{Object.keys(SLOT_ACC).map((k) => <option key={k} value={k}>{SLOT_ACC[k]}</option>)}</select></label>
          <label><span className="lab">Кто везёт</span><select className="pz-in" value={f.carrier.kind} onChange={set('carrier', 'kind')}><option value="supplier">поставщик</option><option value="own">наша машина</option></select></label>
          {f.carrier.kind === 'supplier' && <label><span className="lab">Поставщик</span><input className="pz-in" value={f.carrier.name} onChange={set('carrier', 'name')} /></label>}
          <label><span className="lab">Телефон водителя</span><input className="pz-in" value={f.carrier.phone} onChange={set('carrier', 'phone')} /></label>
          <label><span className="lab">Машина</span><input className="pz-in" value={f.vehicle.make} onChange={set('vehicle', 'make')} placeholder="ГАЗель Next" /></label>
          <label><span className="lab">Госномер</span><input className="pz-in" value={f.vehicle.plate} onChange={set('vehicle', 'plate')} placeholder="А123ВС 777" /></label>
          <label><span className="lab">Что везут</span><input className="pz-in" value={f.cargo} onChange={set('cargo')} placeholder={r.items.map((it) => it.name).join(', ')} /></label>
          <label><span className="lab">Объём</span><input className="pz-in" value={f.volume} onChange={set('volume')} placeholder="2 палеты, 1,2 т" /></label>
          <div className="dz-acts"><button type="button" className="lw-btn-k" onClick={() => save(true)}>Отправить клиенту</button><button type="button" className="lw-btn-l" onClick={() => save(false)}>Сохранить черновик</button><button type="button" className="lw-btn-l" onClick={() => setEdit(false)}>Отмена</button></div>
        </div>}
      </section>);
  }

  function PoModal({ arg, me, d, open, close }) {
    const [pid, id] = arg.split('~');
    const o = PV.order(pid, id);
    const [run, e] = useRun();
    if (!o) return <Modal title="Заказ не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такого заказа нет.</div></Modal>;
    return (
      <Modal size="wide" title={o.id + ' · ' + o.supplier} sub={[o.obj, 'создан ' + dr(o.created), 'ETA ' + dr(o.eta), o.replaces ? 'замена по ' + o.replaces : ''].filter(Boolean).join(' · ')} onClose={close}
        head={<span className={o.late ? 'pill-n cv-over' : PO_CLS[o.status] || 'pill-d'}>{o.late ? 'Опаздывает' : o.statusL}</span>}>
        {err(e)}
        <dl className="cv-meta pz-meta"><div><dt>План</dt><dd>{rub(o.plan)}</dd></div><div><dt>Факт</dt><dd>{rub(o.fact)}</dd></div>
          <div><dt>Разница</dt><dd className={o.variance > 0 ? 'mg-late' : ''}>{o.variance > 0 ? '+' : ''}{rub(o.variance)}{o.variance > 0 ? ' · перерасход' : o.variance < 0 ? ' · экономия' : ''}</dd></div></dl>
        <div className="dz-acts">
          {o.status === 'ordered' && <button type="button" className="lw-btn-k" onClick={() => run(() => P.setOrderStatus(pid, id, 'in_transit', actor(me)))}>Отгружен поставщиком</button>}
          {(o.status === 'in_transit' || o.status === 'partially_delivered') && <button type="button" className="lw-btn-k" onClick={() => open('accept=' + pid + '~' + id)}>{o.status === 'partially_delivered' ? 'Принять допоставку' : 'Принять поставку'}</button>}
          {me.objs && <button type="button" className="lw-btn-l" onClick={() => open('obj=' + pid)}>Объект</button>}
        </div>
        <section className="dz-sec"><h3 className="t">Состав · {o.items.length}</h3>
          <div className="mg-rows">{o.items.map((it) => (
            <div key={it.id} className="r"><span className="tx"><span className="t">{it.name}</span><span className="lab">{[it.qty + ' ' + it.unit, it.stage ? 'этап «' + it.stage + '»' : '',
              it.accepted != null ? 'принято ' + it.accepted + (it.defect ? ' · брак ' + it.defect : '') + (it.shortage ? ' · недостача ' + it.shortage : '') : ''].filter(Boolean).join(' · ')}</span></span>
              <span className="by"><b>{rub(it.price)}</b></span></div>))}</div>
        </section>
        {o.acceptance && <section className="dz-sec"><h3 className="t">Приёмка · {dr(o.acceptance.at)}</h3>
          {o.acceptance.note ? <div className="lab">{o.acceptance.note}</div> : null}
          {(o.acceptance.files || []).length > 0 && <div className="dz-files">{o.acceptance.files.map((f, i) => (f.kind === 'image' ? <a key={i} className="dz-th" href={f.url} target="_blank" rel="noreferrer"><img src={f.url} alt={f.name || ''} /></a> : <a key={i} className="pill-n" href={f.url} target="_blank" rel="noreferrer">{f.name || 'файл'}</a>))}</div>}
        </section>}
        {o.returns.length > 0 && <section className="dz-sec"><h3 className="t">Возвраты · {o.returns.length}</h3>
          <div className="mg-rows">{o.returns.map((r) => (
            <div key={r.id} className="r"><span className="tx"><span className="t">{r.id} · {r.resolution === 'refund' ? 'возврат денег' : 'замена'}</span><span className="lab">{r.lines.map((l) => l.name + ' · ' + l.qty + ' ' + l.unit + ' · ' + l.reason).join(' · ')}{r.replacementPoId ? ' · замена: ' + r.replacementPoId : ''}</span></span>
              <span className="lw-racts"><span className={r.status === 'closed' ? 'pill-g' : 'pill-n'}>{r.statusL}</span>{r.next && <button type="button" className="lw-btn-l" onClick={() => run(() => P.setReturnStatus(pid, r.id, r.next[0], actor(me)))}>{r.next[1]}</button>}</span></div>))}</div>
        </section>}
      </Modal>);
  }

  function AcceptModal({ arg, me, open, close }) {
    const [pid, poId] = arg.split('~');
    const FV = window.ForemanViews; const p = P.get(pid);
    const [d] = React.useState(() => (p && FV && FV.delivery ? FV.delivery(p, poId) : null)); // снимок до приёмки: после неё окно показывает итог
    if (!d || !W.DeliveryAccept) return <Modal title="Поставка не найдена" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Возможно, заказ отменили или его уже приняли.</div></Modal>;
    if (d.closed) return <Modal title={'Поставка ' + d.poId + ' уже принята'} sub={short(d.obj)} onClose={close}><div className="dz-acts"><button type="button" className="lw-btn-l" onClick={() => open('po=' + pid + '~' + poId)}>Открыть заказ</button></div></Modal>;
    const act = (fn) => { try { return fn() || {}; } catch (e) { return { error: e.message }; } };
    const upload = (f) => (window.Files ? window.Files.upload(f, { pid, kind: 'delivery-' + poId }) : Promise.resolve({ url: URL.createObjectURL(f), name: f.name }));
    return <W.DeliveryAccept key={arg} d={d} t0={t0()} onClose={close} upload={upload} onObject={() => open('obj=' + pid)}
      onAccept={(report) => act(() => P.acceptDelivery(pid, poId, report, actor(me)))}
      onReturn={(spec) => act(() => P.createReturn(pid, poId, spec, actor(me)))} />;
  }

  /* ───── объекты: комплектация по этапам и бюджет закупок ───── */
  function ObjectsModal({ d, open, close }) {
    return (
      <Modal size="wide" title="Комплектация объектов" sub={d.objs.length + ' в работе'} onClose={close}>
        {d.objs.length ? <div className="lw-tiles" style={{ marginTop: 18 }}>{d.objs.map((o) => <ObjTile key={o.id} o={o} onOpen={() => open('obj=' + o.id)} />)}</div>
          : <div className="note-s" style={{ marginTop: 18 }}><span>Запущенных объектов пока нет.</span></div>}
      </Modal>);
  }
  const M4 = [['selected', 'Выбрано'], ['approved', 'Согласовано'], ['ordered', 'Заказано'], ['onsite', 'На объекте']];
  function ObjModal({ id, me, d, open, close }) {
    const o = PV.object(id, d);
    const [run, e] = useRun();
    if (!o) return <Modal title="Объект не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такого объекта нет.</div></Modal>;
    const pct = (a, b) => (b ? Math.round(a / b * 100) : 0);
    // чат с прорабом объекта: свой тред закупок, если его ещё нет — создаётся (ProjectStore.chatCreate)
    const talk = () => {
      const p = P.get(id); const th = ((p.chat && p.chat.threads) || []).find((t) => t.members.includes('procurement') && t.members.includes('foreman') && !t.members.includes('client'));
      if (th) { open('chat=project:' + id + ':' + th.id); return; }
      run(() => { P.chatCreate(id, { owner: 'procurement', members: ['foreman'], title: 'Закупки' }, actor(me)); const nt = ((P.get(id).chat || {}).threads || []).find((t) => t.members.includes('procurement') && t.members.includes('foreman') && !t.members.includes('client')); open('chat=project:' + id + ':' + (nt ? nt.id : '')); });
    };
    const stages = o.lines.reduce((a, l) => { (a[l.stage] = a[l.stage] || []).push(l); return a; }, {});
    return (
      <Modal size="wide" title={o.title} sub={[o.client, o.id, o.stage ? 'сейчас: ' + o.stage : ''].filter(Boolean).join(' · ')} onClose={close}
        head={<span className="dz-head">{me.chat && <button type="button" className="cv-arrow-btn" onClick={talk}>Написать прорабу<Ic n="ne" s={14} /></button>}</span>}>
        {err(e)}
        <dl className="cv-meta pz-meta"><div><dt>Бюджет материалов</dt><dd>{rub(o.budget.plan)}</dd></div><div><dt>Заказано</dt><dd>{rub(o.budget.ordered)}</dd></div><div><dt>Осталось заказать</dt><dd>{rub(o.budget.left)}</dd></div></dl>
        <div className="dz-acts">{o.toOrder > 0 && me.orders && <button type="button" className="lw-btn-k" onClick={() => open('queue=' + id)}>Заказать · {o.toOrder}</button>}</div>
        <section className="dz-sec"><h3 className="t">Комплектация по этапам</h3>
          {o.stages.length ? <div className="pz-compl" role="table" aria-label="Комплектация по этапам">
            <div className="pz-cr h" role="row"><span role="columnheader">Этап</span>{M4.slice(1).map(([k, l]) => <span key={k} role="columnheader">{l}</span>)}</div>
            {o.stages.map((s) => (
              <div key={s.stage} className="pz-cr" role="row"><span role="cell" className="t">{s.stage}<span className="lab"> · {s.selected} поз.</span></span>
                {M4.slice(1).map(([k]) => <span key={k} role="cell" className="pz-cc"><span className="mg-bar pz-bar"><i style={{ width: pct(s[k], s.selected) + '%' }} /></span><span className="lab">{s[k]}/{s.selected}</span></span>)}</div>))}
          </div> : <div className="lab">Материалов в проекте пока нет.</div>}
        </section>
        {Object.keys(stages).map((st) => (
          <section key={st} className="dz-sec"><h3 className="t">Этап «{st}»</h3>
            <div className="mg-rows">{stages[st].map((l) => (
              <div key={l.id} className="r"><span className="tx"><span className="t">{l.name}</span><span className="lab">{[l.plan + ' ' + l.unit, l.po ? l.po + ' · ' + l.supplier : 'заказать до ' + l.latest, l.factLate ? 'этап уже идёт' : ''].filter(Boolean).join(' · ')}</span></span>
                <span className="lw-racts"><span className={l.status === 'planned' ? RISK_CLS[l.risk] : l.status === 'on_site' || l.status === 'installed' ? 'pill-g' : 'pill-d'}>{l.status === 'planned' ? PV.RISK_L[l.risk] : PV.PROC_L[l.status] || l.status}</span>
                  {l.po && <button type="button" className="lw-btn-l" onClick={() => open('po=' + l.pid + '~' + l.po)}>Заказ</button>}</span></div>))}</div>
          </section>))}
      </Modal>);
  }

  /* ───── поставщики ───── */
  function SuppliersModal({ d, open, close }) {
    return (
      <Modal size="wide" title="Поставщики" sub={d.sups.length + ' по заказам · срок и брак — по фактическим приёмкам'} onClose={close}>
        {d.sups.length ? <div className="pz-sups">{d.sups.map((s) => (
          <button type="button" key={s.name} className="card pz-sup" onClick={() => open('sup=' + s.name)}>
            <span className="t">{s.name}</span><span className="lab">{s.kinds.join(' · ') || 'материалы'}{s.open ? ' · открытых заказов ' + s.open : ''}</span>
            <dl className="cv-meta"><div><dt>Средний срок</dt><dd>{s.lead != null ? s.lead + ' дн' : '—'}</dd></div><div><dt>Брак</dt><dd>{s.defectPct != null ? s.defectPct + ' %' : '—'}</dd></div><div><dt>Заказано</dt><dd>{rub(s.sum)}</dd></div></dl>
            <span className="lab">{s.leadBy === 'fact' ? 'срок — по ' + s.accepted + ' ' + plural(s.accepted, 'приёмке', 'приёмкам', 'приёмкам') : s.leadBy === 'eta' ? 'срок — по обещанным датам, приёмок ещё не было' : ''}{s.returns ? ' · возвратов ' + s.returns : ''}</span>
          </button>))}</div> : <div className="note-s" style={{ marginTop: 18 }}><span>Поставщики появятся с первыми заказами.</span></div>}
      </Modal>);
  }
  function SupModal({ name, d, open, close }) {
    const s = d.sups.find((x) => x.name === name);
    if (!s) return <Modal title="Поставщик не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>У этого поставщика нет заказов.</div></Modal>;
    return (
      <Modal size="wide" title={s.name} sub={(s.kinds.join(' · ') || 'материалы') + ' · ' + s.orders + ' ' + plural(s.orders, 'заказ', 'заказа', 'заказов')} onClose={close}>
        <dl className="cv-meta pz-meta"><div><dt>Средний срок</dt><dd>{s.lead != null ? s.lead + ' дн' : '—'}</dd></div><div><dt>Доля брака</dt><dd>{s.defectPct != null ? s.defectPct + ' %' : 'приёмок нет'}</dd></div>
          <div><dt>Открытых заказов</dt><dd>{s.open}</dd></div><div><dt>Возвратов</dt><dd>{s.returns}</dd></div></dl>
        <section className="dz-sec"><h3 className="t">Заказы</h3>
          <div className="mg-list">{s.list.map((o) => (
            <button type="button" key={o.pid + o.id} className="mg-deal" onClick={() => open('po=' + o.pid + '~' + o.id)}>
              <span className="t">{o.id}</span><span className="lab">{short(o.obj)} · ETA {dr(o.eta)}</span><span className="r"><b>{rub(o.sum)}</b><span className="lab">{o.statusL}</span></span>
            </button>))}</div>
        </section>
      </Modal>);
  }

  /* ───── замены: цепочка закупщик → дизайнер (чистовые) → менеджер → клиент; возвраты — в окне заказа ───── */
  const CHAIN = [['pending_designer', 'Дизайнер'], ['pending_manager', 'Менеджер'], ['pending_client', 'Клиент']];
  function RplModal({ me, d, open, close }) {
    const rets = d.returns.filter((r) => r.status !== 'closed');
    return (
      <Modal size="wide" title="Замены и возвраты" sub={d.repl.length + ' ' + plural(d.repl.length, 'замена', 'замены', 'замен') + ' · ' + rets.length + ' открытых возвратов'} onClose={close}
        head={me.rpl ? <button type="button" className="lw-btn-k" onClick={() => open('rplnew')}>Новая заявка</button> : null}>
        {rets.length > 0 && <section className="dz-sec"><h3 className="t">Возвраты поставщикам</h3>
          <div className="mg-list">{rets.map((r) => (
            <button type="button" key={r.pid + r.id} className="mg-deal" onClick={() => open('po=' + r.pid + '~' + r.po)}>
              <span className="t">{r.id} · {r.supplier}</span><span className="lab">{short(r.obj)} · по {r.po} · {r.resolution === 'refund' ? 'возврат денег' : 'замена'}</span><span className="r"><b>{rub(r.sum)}</b><span className="lab">{PV.RET_L[r.status]}</span></span>
            </button>))}</div></section>}
        <section className="dz-sec"><h3 className="t">Замены материалов</h3>
          {d.repl.length ? <div className="cv-cards">{d.repl.map((r) => (
            <div key={r.pid + r.id} className="card cv-dec">
              <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{r.orig.name} → {r.prop.name}</div><div className="lab" style={{ marginTop: 3 }}>{[short(r.obj), r.room, r.reasonL].filter(Boolean).join(' · ')}</div></div>
                <span className={r.status === 'approved' ? 'pill-g' : r.status === 'rejected' ? 'pill-d' : 'pill-n'}>{PV.RPL_L[r.status] || r.status}</span></div>
              <dl className="cv-meta"><div><dt>Было</dt><dd>{[r.orig.supplier !== '—' ? r.orig.supplier : '', rub(r.orig.price), r.orig.lead + ' дн'].filter(Boolean).join(' · ')}</dd></div><div><dt>Аналог</dt><dd>{r.prop.supplier} · {rub(r.prop.price)} · {r.prop.lead} дн</dd></div>
                <div><dt>Разница</dt><dd className={r.priceDelta > 0 ? 'mg-late' : ''}>{r.priceDelta > 0 ? '+' : ''}{rub(r.priceDelta)} · {r.leadDelta > 0 ? '+' : ''}{r.leadDelta} дн</dd></div></dl>
              <ol className="mg-steps pz-chain" aria-label="Согласование">{CHAIN.filter(([st]) => r.kind === 'finish' || st !== 'pending_designer').map(([st, l]) => {
                const idx = CHAIN.findIndex((x) => x[0] === st); const cur = CHAIN.findIndex((x) => x[0] === r.status);
                return <li key={st} className={r.status === 'approved' || (cur > idx) ? 'is-done' : cur === idx ? 'is-cur' : ''}>{l}</li>; })}</ol>
              {(r.chain || []).filter((c) => c.comment).map((c, i) => <div key={i} className="lab" style={{ marginTop: 6 }}><b>{PV.RU[c.role] || c.role}:</b> {c.comment}</div>)}
            </div>))}</div> : <div className="note-s"><span>Заявок на замену нет — «Замена» есть у каждой позиции в очереди закупок.</span></div>}
        </section>
      </Modal>);
  }
  function RplNewModal({ arg, me, d, open, close }) {
    const [pid, matId] = (arg || '').split('~');
    const l = matId ? d.lines.find((x) => x.pid === pid && x.id === matId) : null;
    const [f, setF] = React.useState({ name: '', supplier: '', price: l ? String(l.purchase || '') : '', lead: '3', reason: 'unavailable', note: '' });
    const [run, e] = useRun();
    const set = (k) => (x) => setF(Object.assign({}, f, { [k]: x.target.value }));
    if (!l) {
      const cand = d.lines.filter((x) => x.status !== 'on_site' && x.status !== 'installed');
      return (
        <Modal size="wide" title="Какую позицию заменить?" sub={cand.length + ' ' + plural(cand.length, 'позиция', 'позиции', 'позиций') + ' ещё не на объекте'} onClose={close}>
          <div className="mg-list">{cand.map((x) => (
            <button type="button" key={x.pid + x.id} className="mg-deal" onClick={() => open('rplnew=' + x.pid + '~' + x.id)}>
              <span className="t">{x.name}</span><span className="lab">{short(x.obj)} · этап «{x.stage}» · {x.plan} {x.unit}</span><span className="r"><b>{rub(x.purchase)}</b><span className="lab">{PV.PROC_L[x.status]}</span></span>
            </button>))}</div>
        </Modal>);
    }
    const submit = () => { if (run(() => P.createReplacement(pid, { matId, name: f.name, supplier: f.supplier, price: +f.price || 0, lead: +f.lead || 0, reason: f.reason, note: f.note }, actor(me)))) open('rpl'); };
    return (
      <Modal title="Заявка на замену" sub={[l.name, short(l.obj), l.plan + ' ' + l.unit, rub(l.purchase), l.kind === 'finish' ? 'согласуют дизайнер и менеджер' : 'согласует менеджер'].join(' · ')} onClose={close}>
        {err(e)}
        <div className="mg-form">
          <input value={f.name} onChange={set('name')} placeholder="Аналог — наименование" aria-label="Аналог" autoFocus />
          <SupplierInput d={d} value={f.supplier} onChange={(v) => setF(Object.assign({}, f, { supplier: v }))} />
          <select value={f.reason} onChange={set('reason')} aria-label="Причина">{Object.entries(P.RPL_REASON || {}).map(([k, t]) => <option key={k} value={k}>{t}</option>)}</select>
          <input value={f.price} onChange={set('price')} inputMode="numeric" placeholder="Цена за позицию, ₽" aria-label="Цена" />
          <input value={f.lead} onChange={set('lead')} inputMode="numeric" placeholder="Срок поставки, дн" aria-label="Срок поставки" />
          <textarea className="cv-ta" rows={2} value={f.note} onChange={set('note')} placeholder="Почему аналог подходит" aria-label="Комментарий" />
        </div>
        <div className="dz-acts"><button type="button" className="lw-btn-l" onClick={() => open('rplnew')}>Другая позиция</button><button type="button" className="lw-btn-k" disabled={!f.name.trim()} onClick={submit}>Отправить на согласование</button></div>
      </Modal>);
  }

  /* ───── чаты объектов, где есть закупки ───── */
  // вложение: видео — с плеером, картинка — картинкой, остальное — ссылкой (как в чатах других кабинетов)
  function Media({ m }) {
    const kind = m.kind || (/\.(mp4|mov|webm)(\?|$)/i.test(m.url) ? 'video' : /\.(png|jpe?g|gif|webp|avif)(\?|$)/i.test(m.url) || /^data:image/.test(m.url) ? 'image' : 'file');
    if (kind === 'video') return <video src={m.url} controls preload="metadata" playsInline style={{ maxWidth: '100%', borderRadius: 12 }} />;
    if (kind === 'image' || kind === 'photo') return <img src={m.url} alt={m.name || ''} />;
    return <a className="pill-n" href={m.url} target="_blank" rel="noreferrer">{m.name || 'Вложение'}</a>;
  }
  function ChatModal({ d, sel, open, close }) {
    const [, pid0, tid0] = (sel || '').split(':');
    const cur = d.chats.find((c) => c.pid === pid0 && (!tid0 || c.id === tid0)) || d.chats[0] || null;
    const [txt, setTxt] = React.useState('');
    const thread = cur ? ((P.get(cur.pid) || {}).chat || { threads: [] }).threads.find((t) => t.id === cur.id) : null;
    const send = () => { if (!txt.trim() || !thread) return; P.chatSend(cur.pid, thread.id, { from: 'procurement', t: txt.trim() }); setTxt(''); };
    return (
      <Modal size="wide" title="Чаты" sub="объекты, где есть закупки" onClose={close}>
        <div className="lw-chat">
          <div className="lw-chl">{d.chats.map((c) => (
            <button type="button" key={c.pid + c.id} className={'lw-chr' + (cur && c.pid === cur.pid && c.id === cur.id ? ' on' : '') + (c.isNew ? ' is-new' : '')} onClick={() => open('chat=project:' + c.pid + ':' + c.id)}>
              <span className="t">{short(c.obj)}<span className="lab"> · {c.title}</span></span><span className="lab">{c.who ? c.who + ': ' : ''}{c.text}</span><span className="tm lab">{when(c.at)}</span></button>))}</div>
          <div className="lw-chv">{thread ? <React.Fragment>
            <div className="lw-chh"><b>{cur.obj}</b><span className="lab">{thread.members.filter((m) => m !== 'procurement').map((m) => PV.RU[m] || m).join(', ')}</span>
              <button type="button" className="cv-arrow-btn" onClick={() => open('obj=' + cur.pid)}>Объект<Ic n="ne" s={14} /></button></div>
            <div className="lw-msgs cv-scroll">{thread.msgs.map((m) => (
              <div key={m.id} className={'lw-msg' + (m.from === 'procurement' ? ' mine' : '')}><span className="lab">{PV.RU[m.from] || m.from} · {when(m.at)}</span>
                {m.t ? <span className="b">{m.t}</span> : null}{m.media && m.media.url ? <Media m={m.media} /> : null}</div>))}</div>
            <div className="lw-send"><input value={txt} onChange={(e) => setTxt(e.target.value)} placeholder="Написать" aria-label="Сообщение" onKeyDown={(e) => { if (e.key === 'Enter') send(); }} /><button type="button" className="lw-btn-k" disabled={!txt.trim()} onClick={send}>Отправить</button></div>
          </React.Fragment> : <div className="empty">Переписки пока нет. Напишите прорабу из окна объекта.</div>}</div>
        </div>
      </Modal>);
  }

  function FeedModal({ open, close }) {
    const rows = PV.feed(60);
    return (
      <Modal size="wide" title="Лента закупок" sub="заказы, поставки, приёмки, замены и возвраты по объектам" onClose={close}>
        {rows.length ? <div className="mg-rows" style={{ marginTop: 12 }}>{rows.map((r, i) => (
          <button type="button" key={i} className="r mg-row" onClick={() => open('obj=' + r.pid)}><span className="tx"><span className="t">{r.text}</span><span className="lab">{short(r.obj)}{r.who ? ' · ' + r.who : ''}</span></span><span className="by lab">{when(r.at)}</span></button>))}</div>
          : <div className="note-s" style={{ marginTop: 18 }}><span>Событий по материалам пока нет.</span></div>}
      </Modal>);
  }

  /* ───── данные и маршрутизация окон ───── */
  function collect() {
    const S = P;
    const d = { lines: PV.lines(), orders: PV.orders(), requests: PV.requests(), returns: S ? S.views.returns() : [], repl: S ? S.views.replacements() : [] };
    d.f = PV.facts(d); d.objs = PV.objects(d); d.sups = PV.suppliers(d); d.chats = PV.chats();
    return d;
  }
  function App() {
    useStore();
    const me = useMe();
    const [modal, open, close] = useHash();
    // при открытии кабинета — пересчитать заявки на доставку по всем проектам (как прежний кабинет)
    React.useEffect(() => { if (P) P.list().forEach((p) => { try { P.refreshDeliveries(p.id); } catch (e) { /* проект без графика */ } }); }, []);
    if (!PV) return <div className="cv-page"><div className="empty">Кабинет не загрузился. Обновите страницу.</div></div>;
    if (me.deny) return <W.Denied why={me.deny} />;
    const d = collect();
    const arg = (k) => (modal.indexOf(k + '=') === 0 ? modal.slice(k.length + 1) : null);
    const has = (k) => modal === k || arg(k) != null;
    const zone = has('queue') || has('orders') || has('dl') || has('po') || has('accept') ? 'orders' : has('obj') || has('objects') ? 'objs' : has('suppliers') || has('sup') ? 'sups' : has('rpl') || has('rplnew') ? 'rpl' : has('chat') ? 'chat' : has('feed') ? 'home' : '';
    const NAMES = { orders: 'Раздел «Заказы»', objs: 'Раздел «Комплектация»', sups: 'Раздел «Поставщики»', rpl: 'Раздел «Замены и возвраты»', chat: 'Раздел «Чаты»', home: 'Лента' };
    const ok = zone ? me[zone] : true;
    return (
      <React.Fragment>
        {me.home ? <Home me={me} d={d} open={open} /> : <W.Denied title="Главная закрыта" why="Главная кабинета не входит в выданный доступ — открытые вам разделы работают по своим ссылкам." />}
        {zone && !ok && <W.Closed what={NAMES[zone]} onClose={close} />}
        {ok && has('queue') && <QueueModal key={modal} me={me} d={d} sel={arg('queue')} open={open} close={close} />}
        {ok && has('orders') && <OrdersModal key={modal} d={d} sel={arg('orders')} open={open} close={close} />}
        {ok && arg('dl') && <DlModal key={arg('dl')} arg={arg('dl')} me={me} d={d} open={open} close={close} />}
        {ok && arg('po') && <PoModal key={arg('po')} arg={arg('po')} me={me} d={d} open={open} close={close} />}
        {ok && arg('accept') && <AcceptModal key={arg('accept')} arg={arg('accept')} me={me} open={open} close={close} />}
        {ok && modal === 'objects' && <ObjectsModal d={d} open={open} close={close} />}
        {ok && arg('obj') && <ObjModal key={arg('obj')} id={arg('obj')} me={me} d={d} open={open} close={close} />}
        {ok && modal === 'suppliers' && <SuppliersModal d={d} open={open} close={close} />}
        {ok && arg('sup') && <SupModal key={arg('sup')} name={arg('sup')} d={d} open={open} close={close} />}
        {ok && modal === 'rpl' && <RplModal me={me} d={d} open={open} close={close} />}
        {ok && has('rplnew') && <RplNewModal key={modal} arg={arg('rplnew')} me={me} d={d} open={open} close={close} />}
        {ok && has('chat') && <ChatModal d={d} sel={arg('chat')} open={open} close={close} />}
        {ok && modal === 'feed' && <FeedModal open={open} close={close} />}
      </React.Fragment>);
  }

  window.ProcurementV2 = { App, collect, myInbox };
  if (!window.__PROCUREMENT_V2_NO_MOUNT) ReactDOM.createRoot(document.getElementById('root')).render(<App />);
})();
