/* manager-v2-app.jsx — кабинет менеджера в виде кабинета клиента (KSH-455): шапка, главная со сводкой, разделы — окнами по #hash.
   Собран из библиотеки виджетов lk-widgets.jsx (window.LKW) на данных manager-views.js (window.ManagerViews).
   Главная: сводка продаж и денег, «Сейчас важно», сделки на закрытие, деньги клиентов, ждёт ответа клиенту, новые заявки без менеджера,
   мои проекты, чаты, «Мои задачи» (общий таск-менеджер: задачи сделок, решения, очередь).
   Окна: #deals[=lost|closing] — все сделки по этапам; #deal=<id> — сделка: путь воронки, следующий шаг, что держит, команда, чат с клиентом,
   «Сделка проиграна» с причиной (ManagerViews.markLost) и «Вернуть в работу»; #lost — проигранные и причины; #projects; #proj=<id> — проект:
   этап и ход, график оплат, акты у клиента, «Напомнить клиенту» (сообщение в чат клиента); #money — деньги клиентов; #chat[=<src>:<id>:<tid>];
   #dec=<kind>:<pid> — решение из «Мои задачи» (замена материала, цена мастера выше цены компании, правки клиента по спецификации, оплата этапа).
   Действия — функции, что и в прежнем кабинете: ProjectStore.decideReplacement / resubmitMaterial / chatSend / chatEnsureClientThread,
   ExecPrices.decideOffer, DealChat.send, DealTeam.assign, FormInstances.completeTask / acknowledgeRoute. Мастер сделки (калькулятор) и полная
   карточка сделки (договоры, ДП, документы, доставки) — прежние экраны: ссылки из окна сделки. Остальные разделы прежнего кабинета
   (Календарь, Запуск проекта, Клиенты, Воронка, Профиль) — в «Ещё». */
(function () {
  'use strict';
  const P = window.ProjectStore, MV = window.ManagerViews, W = window.LKW, A = window.AccessStore, FV = window.ForemanViews;
  const { Ic, Header, Modal, FactsBand, NowCards, RowsW, ChatsW, PhaseMini, rub, plural, cap, dateR, short } = W;
  const t0 = () => new Date().toISOString().slice(0, 10);
  const dm = (d) => (d ? String(d).slice(8, 10) + '.' + String(d).slice(5, 7) + '.' + String(d).slice(2, 4) : '');

  function useMe() {
    const acc = A && A.currentAccount ? A.currentAccount() : null;
    const ac = W.access(acc, ['manager', 'super']);
    // главная (today), «Сделки» и «Чаты» — отдельные разделы доступа: выключенная главная не закрывает выданные разделы
    const home = ac.zone('today'), deals = ac.zone('deals'), chat = ac.zone('chats');
    return { id: acc ? acc.profileId : null, name: (acc && acc.name) || 'Менеджер', role: acc && acc.role === 'super' ? 'super' : 'manager',
      deny: ac.deny || (home || deals || chat ? '' : 'Кабинет менеджера не входит в выданный доступ.'), home, deals, chat };
  }
  // перерисовка: проекты, сделки (карточки, сметы, формы), чаты, решения, проигранные
  const EVS = ['storage', 'obitel:sync', 'obitel:deal-chat', 'obitel:deal-docs', 'obitel:deal-lost', 'obitel:exec-prices', 'remontpro:forms-updated', 'remontpro:intake-updated', 'remontpro:measure-submitted', 'remontpro:intake-route'];
  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)); }, []);
  }
  function useHash() {
    const read = () => decodeURIComponent((window.location.hash || '').replace('#', ''));
    const [m, setM] = React.useState(read());
    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 sumL = (n) => (n > 0 ? rub(n) : 'без сметы');
  const go = (url) => { window.location.href = url; };
  // ссылки на прежние экраны: та же ссылка менеджера (?t=), без своего #окна
  const keep = (page, extra) => { const q = new URLSearchParams(window.location.search); Object.keys(extra || {}).forEach((k) => q.set(k, extra[k])); const s = q.toString(); return page + (s ? '?' + s : ''); };
  const dealCard = (id, tab) => keep('manager-clients.html', Object.assign({ deal: id }, tab ? { tab: tab } : {}));
  const dealWizard = (id) => 'calculator.html?role=manager&deal=' + encodeURIComponent(id);

  const NAV = [['', 'Главная'], ['deals', 'Сделки'], ['projects', 'Проекты'], ['money', 'Деньги'], ['chat', 'Чаты']];
  const PHONE = ['', 'deals', 'projects', 'chat'];
  const MORE = [['lost', 'Проигранные сделки'], ['tools.html?role=manager', 'Инструменты'], ['manager-handoff.html', 'Запуск проекта'], ['manager-calendar.html', 'Календарь'], ['manager-clients.html', 'Клиенты'], ['manager-funnel.html', 'Воронка и прогноз'], ['manager-profile.html', 'Профиль']];
  const COLS = [['new', 'Новые'], ['card', 'Карточка и замер'], ['dp', 'Дизайн-проект'], ['estimate', 'Смета'], ['negotiation', 'Согласование'], ['contract', 'Договор и запуск']];
  const COL_OF = { new: 'new', contacted: 'new', qualified: 'card', card: 'card', measure_pending: 'card', measurement_scheduled: 'card', measurement_done: 'card', brief: 'dp', dp: 'dp', estimate: 'estimate', estimate_ready: 'estimate', estimate_in_progress: 'estimate', estimate_sent: 'estimate', negotiation: 'negotiation', contract: 'contract', contract_preparing: 'contract', contract_signed: 'contract', prepayment_received: 'contract' };
  const stageL = (d) => MV.STAGE_LABEL[d.stage] || d.stage;
  const active = (deals) => deals.filter((d) => d.stage !== 'won' && d.stage !== 'lost');

  /* ───── «Сейчас важно»: строка очереди → карточка (кнопка ведёт туда же, куда в прежнем кабинете) ───── */
  function qAct(q, open) {
    if (q.kind === 'debt' && q.pid) return () => open('proj=' + q.pid);
    if (q.url) return () => go(q.url);
    if (q.deal) return () => open('deal=' + q.deal);
    return () => open('deals');
  }
  const nowRows = (queue, open) => queue.slice(0, 3).map((q, i) => ({ key: (q.taskId || q.routeId || q.kind + (q.deal || q.pid || '')) + i, kindL: q.kindL, late: q.sev === 'red', title: q.title, sub: q.sub, right: q.sev === 'red' ? 'срочно' : '', act: { text: q.act || 'Открыть', onClick: qAct(q, open) } }));

  /* «Мои задачи»: задачи сделок (TaskViews.inbox), решения (decisionRows: строка открывает окно #dec), очередь без дублей задач команды */
  function myInbox(queue, open, me) {
    const T = window.TaskViews; if (!T) return [];
    const dec = (T.decisionRows ? T.decisionRows('manager') : []).map((r) => Object.assign({}, r, { act: () => (r.dkind === 'sub' ? go(keep('manager-handoff.html')) : open('dec=' + r.dkind + ':' + (r.pid || ''))) }));
    const q = queue.filter((x) => !x.taskId).map((x, i) => ({ id: 'mq:' + (x.routeId || [x.kind, x.deal || x.pid, x.title].join(':') || i), kind: 'queue', kindL: x.kindL, title: x.title,
      obj: x.deal ? 'Сделка ' + x.deal : '', pid: x.deal || x.pid || '', dealId: x.deal || undefined, sub: x.sub, now: x.sev === 'red' || undefined, status: 'open', statusL: x.kindL,
      next: { who: 'me', l: x.act || 'Открыть' }, act: qAct(x, open) }));
    const deal = T.inbox ? T.inbox('manager', me.name || 'manager', t0()).map((r) => Object.assign({}, r, { act: () => { if (r.kind === 'returned' && r.dealId && r.key && T.markTaskSeen) T.markTaskSeen(r.dealId, r.key, { role: 'manager', name: me.name }); if (r.dealId) open('deal=' + r.dealId); else if (r.href) go(r.href); } })) : [];
    const order = (r) => (r.late ? 0 : r.now ? 1 : 2);
    return dec.concat(q, deal).sort((a, b) => order(a) - order(b) || String(a.due || '9999').localeCompare(String(b.due || '9999')));
  }

  /* ───── главная ───── */
  function Home({ me, data, open }) {
    const { deals, queue, projs, money, chats, wait, leads } = data;
    const act = active(deals);
    const closing = act.filter((d) => d.prob >= 60).sort((a, b) => b.prob - a.prob || b.amount - a.amount);
    const pipe = act.reduce((a, d) => a + (d.amount || 0), 0), noSum = act.filter((d) => !(d.amount > 0)).length;
    const stuck = act.filter((d) => d.overdue).length;
    const red = queue.filter((q) => q.sev === 'red').length;
    const acts = projs.reduce((a, p) => a + p.acts.length, 0);
    const lost = MV.lostReport(t0().slice(0, 8) + '01');
    const first = (me.name || '').split(' ')[0];
    const T = window.TaskViews;
    const newDeal = () => { const C = window.Calc2; const id = C && C.nextDealId ? C.nextDealId() : 'D-' + Date.now().toString(36).toUpperCase(); go('calculator.html?deal=' + encodeURIComponent(id) + '&role=manager'); };
    const moneyRows = money.overdue.concat(money.expected).slice(0, 6).map((x) => ({ key: x.id, ic: 'wallet', late: x.state === 'overdue', title: x.client + ' · ' + rub(x.amount), sub: x.title + ' · ' + short(x.obj), right: x.state === 'overdue' ? 'просрочка ' + x.days + ' дн' : 'до ' + dm(x.due), onClick: () => open('proj=' + x.pid) }));
    return (
      <div className="cv-page lw-page">
        <Header nav={NAV} more={MORE} phone={PHONE} 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()} · {act.length} {plural(act.length, 'сделка', 'сделки', 'сделок')} в работе</div><h1 className="cv-hello">{greeting()}{first ? ', ' + first : ''}</h1></div>
            <div className="cv-hero-act">
              <button type="button" className="btn-l" onClick={newDeal}>Новая сделка</button>
              {red ? <button type="button" className="btn-k" onClick={() => document.getElementById('mg-tasks') && document.getElementById('mg-tasks').scrollIntoView({ behavior: 'smooth' })}>Срочно · {red}</button>
                : <span className="pill-g"><span className="dot" />Срочного нет</span>}
            </div>
          </section>

          <FactsBand label="Сводка продаж"
            live={<span className="now">{chats.filter((c) => c.isNew).length ? <React.Fragment><i className="cv-pulse" aria-hidden="true" /><span>Новые сообщения: {chats.filter((c) => c.isNew).map((c) => short(c.obj)).filter((x, i, a) => a.indexOf(x) === i).slice(0, 3).join(', ')}</span></React.Fragment> : 'Новых сообщений нет'}</span>}
            facts={[
              { v: act.length + ' ' + plural(act.length, 'сделка', 'сделки', 'сделок'), lab: stuck ? stuck + ' ' + plural(stuck, 'стоит', 'стоят', 'стоят') + ' без движения' : 'В работе · без застрявших', onClick: () => open('deals') },
              { v: pipe > 0 ? rub(pipe) : '—', lab: 'В воронке' + (noSum ? ' · ' + noSum + ' без сметы' : ''), onClick: () => open('deals') },
              { v: closing.length + ' на закрытие', lab: 'Смета на согласовании или договор', onClick: () => open('deals=closing') },
              { v: projs.length + ' ' + plural(projs.length, 'проект', 'проекта', 'проектов'), lab: acts ? acts + ' ' + plural(acts, 'акт', 'акта', 'актов') + ' ждут подписи клиента' : 'В работе · актов на подписи нет', onClick: () => open('projects') },
              { v: money.overdueSum ? rub(money.overdueSum) : rub(money.expectedSum), lab: money.overdueSum ? 'Просрочено · ' + money.overdue.length + ' ' + plural(money.overdue.length, 'счёт', 'счёта', 'счетов') : 'Ожидаем оплату · ' + money.expected.length + ' ' + plural(money.expected.length, 'счёт', 'счёта', 'счетов'), late: money.overdueSum > 0, onClick: () => open('money') },
            ]} />

          <NowCards rows={nowRows(queue, open)} />

          <section className="cv-two lw-two">
            {/* сделки: на закрытие (вероятность от 60 %) — сверху, дальше застрявшие и остальные */}
            <RowsW id="mg-deals-h" title="Мои сделки" n={closing.length} rows={act.slice().sort((a, b) => (b.prob >= 60) - (a.prob >= 60) || (b.overdue ? 1 : 0) - (a.overdue ? 1 : 0) || b.prob - a.prob).slice(0, 8).map((d) => ({ key: d.id, ic: 'doc', late: d.overdue, title: d.client, sub: [stageL(d), d.nextAction].filter((x, i, a) => x && a.indexOf(x) === i).join(' · '), right: sumL(d.amount), rightLab: d.prob >= 60 ? 'на закрытие' : d.overdue ? d.tis + ' дн без движения' : '', onClick: () => open('deal=' + d.id) }))}
              empty="Сделок в работе нет. Новая сделка — кнопка вверху." onOpen={() => open('deals')} text={'Все сделки' + (act.length > 8 ? ' · ' + act.length : '')} className="is-grow" />
            <div className="cv-side">
              <RowsW id="mg-money-h" title="Деньги клиентов" n={money.overdue.length} rows={moneyRows} empty="Выставленных неоплаченных счетов нет." onOpen={() => open('money')} text="Все счета" max={300} />
              <ChatsW chats={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>

          {(wait.length > 0 || leads.length > 0) && <section className={wait.length && leads.length ? 'cv-two lw-two' : 'mg-one'}>
            {wait.length > 0 && <RowsW id="mg-wait-h" title="Ждёт ответа клиенту" n={wait.filter((x) => x.overdue).length} rows={wait.slice(0, 8).map((r) => ({ key: (r.pid || r.dealId) + r.id, ic: r.kind === 'chat' ? 'chat' : 'clock', late: r.overdue, title: (r.client || r.object || r.dealId || '') + ' · ' + r.title, sub: r.text, onClick: () => (r.pid ? open(r.kind === 'chat' ? 'chat' : 'proj=' + r.pid) : r.dealId ? open('deal=' + r.dealId) : null),
              acts: r.phone ? <a className="lw-btn-l" href={'tel:' + String(r.phone).replace(/[^\d+]/g, '')}>Позвонить</a> : null }))} empty="Клиенты ничего не ждут." />}
            {leads.length > 0 && <LeadsW leads={leads} open={open} />}
          </section>}

          <section className="lw-sec" aria-labelledby="mg-proj-h">
            <div className="lw-sech"><h2 className="h2" id="mg-proj-h">Мои проекты</h2><button type="button" className="cv-arrow-btn" onClick={() => open('projects')}>Все проекты{projs.length > 4 ? ' · ' + projs.length : ''}<Ic n="ne" s={14} /></button></div>
            {projs.length ? <div className="lw-tiles">{projs.slice(0, 4).map((p) => <ProjTile key={p.id} p={p} onOpen={() => open('proj=' + p.id)} />)}</div>
              : <div className="note-s" style={{ marginTop: 12 }}><span>Запущенных проектов пока нет.</span></div>}
          </section>

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

          {lost.total > 0 && <section className="cv-w lw-gw" aria-labelledby="mg-lost-h">
            <W.WHead id="mg-lost-h" title="Проигранные в этом месяце" sub={lost.total + ' ' + plural(lost.total, 'сделка', 'сделки', 'сделок') + ' · ' + rub(lost.amount)} text="Причины" onOpen={() => open('lost')} />
            <div className="mg-reasons">{lost.by.map((r) => <span key={r.key} className="pill-n">{r.label} · {r.count}</span>)}</div>
          </section>}
        </main>
      </div>);
  }

  // плитка проекта: путь фаз (ForemanViews.objects — как у прораба), флажки менеджера — просроченные счета и акты у клиента
  function ProjTile({ p, onOpen }) {
    const o = FV && FV.objects ? FV.objects([P.get(p.id)].filter(Boolean), t0())[0] : null;
    const flags = [p.overdueN ? p.overdueN + ' ' + plural(p.overdueN, 'счёт просрочен', 'счёта просрочены', 'счетов просрочены') : '', p.acts.length ? p.acts.length + ' ' + plural(p.acts.length, 'акт', 'акта', 'актов') + ' у клиента' : '', p.handoff ? 'ждёт прораба' : ''].filter(Boolean);
    return (
      <button type="button" className="lw-tile" onClick={onOpen} aria-label={'Открыть проект ' + p.title}>
        <span className="lw-tph">{o && o.cover ? <img src={o.cover} alt="" loading="lazy" /> : <span className="ph0"><Ic n="key" s={28} /></span>}
          {flags.length ? <span className="fl">{flags.map((f) => <span key={f} className={/просроч/.test(f) ? 'is-late' : ''}>{f}</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">{p.title}</span>
          <span className="lab">{[p.client, p.area ? p.area + ' м²' : ''].filter(Boolean).join(' · ')}</span>
          {o && o.path ? <PhaseMini path={o.path} /> : null}
          <span className="row"><span className="st">{p.stage}</span><span className="pc">{p.pct} %</span></span>
          <span className="lab">Оплачено {rub(p.paid)} из {rub(p.contract)}</span>
        </span>
      </button>);
  }

  /* новые заявки без менеджера: взять в работу или отдать другому (DealTeam.assign — как раньше) */
  function LeadsW({ leads, open }) {
    const T = window.DealTeam;
    const [pick, setPick] = React.useState(null);
    if (!T) return <div />;
    const me = T.currentManager ? T.currentManager() : null; const mgrs = T.managers ? T.managers() : [];
    const take = (d, who) => { T.assign(d.dealId, 'manager', { id: who.id, name: who.name, how: who.id === (me && me.id) ? 'взял в работу' : 'назначен' }); setPick(null); };
    return (
      <section className="cv-w cv-apr-w lw-rows" aria-labelledby="mg-leads-h">
        <W.WHead id="mg-leads-h" title="Новые заявки без менеджера" n={leads.length} />
        {!leads.length ? <div className="note-s" style={{ marginTop: 14 }}><Ic n="check" c="#2F5D3A" s={16} /><span>Все заявки распределены.</span></div>
          : <div className="rows cv-scroll" tabIndex={0} style={{ maxHeight: 330 }}>{leads.map((d) => (
            <div key={d.dealId} className="r lw-r">
              <span className="ic"><Ic n="user" s={15} /></span>
              <button type="button" className="tx lw-rt" onClick={() => open('deal=' + d.dealId)}><span className="t">{d.client}</span><span className="lab">{[d.address, d.sourceLabel && d.sourceLabel !== '—' ? d.sourceLabel : '', d.stage && d.stage.label].filter(Boolean).join(' · ')}</span>
                {pick === d.dealId && <span className="mg-pick">{mgrs.map((m) => <span key={m.id} role="button" tabIndex={0} className="pill-n" onClick={(e) => { e.stopPropagation(); take(d, m); }} onKeyDown={(e) => { if (e.key === 'Enter') take(d, m); }}>{m.name}</span>)}{!mgrs.length && <span className="lab">Аккаунтов менеджеров нет</span>}</span>}</button>
              <span className="lw-racts">
                {me && <button type="button" className="lw-btn-k" onClick={() => take(d, me)}>Взять</button>}
                <button type="button" className="lw-btn-l" onClick={() => setPick(pick === d.dealId ? null : d.dealId)}>{me ? 'Другому' : 'Назначить'}</button>
              </span>
            </div>))}</div>}
      </section>);
  }

  /* ───── сделки ───── */
  function DealsModal({ data, sel, open, close }) {
    const [tab, setTab] = React.useState(sel === 'lost' ? 'lost' : sel === 'closing' ? 'closing' : 'all');
    const act = active(data.deals);
    const lost = data.deals.filter((d) => d.stage === 'lost');
    const won = data.deals.filter((d) => d.stage === 'won');
    const list = tab === 'closing' ? act.filter((d) => d.prob >= 60) : tab === 'lost' ? lost : tab === 'won' ? won : act;
    const row = (d) => (
      <button type="button" key={d.id} className="mg-deal" onClick={() => open('deal=' + d.id)}>
        <span className="t">{d.client}<span className="lab"> · {d.id}</span></span>
        <span className="lab">{tab === 'lost' ? 'Причина: ' + d.lostReasonL + (d.lost.note ? ' — ' + d.lost.note : '') + ' · ' + dm(d.lost.at) : tab === 'all' ? d.nextAction || stageL(d) : [stageL(d), d.nextAction].filter((x, i, l) => x && l.indexOf(x) === i).join(' · ')}</span>
        <span className="r"><b>{sumL(d.amount)}</b>{tab !== 'lost' && d.tis ? <span className={'lab' + (d.overdue ? ' mg-late' : '')}>{d.tis} дн без движения</span> : null}</span>
      </button>);
    const TABS = [['all', 'В работе', act.length], ['closing', 'На закрытие', act.filter((d) => d.prob >= 60).length], ['won', 'Запущены', won.length], ['lost', 'Проигранные', lost.length]];
    return (
      <Modal size="wide" title="Сделки" sub={act.length + ' в работе · ' + rub(act.reduce((a, d) => a + (d.amount || 0), 0)) + ' в воронке'} onClose={close}
        head={<a className="lw-btn-l" href={keep('manager-funnel.html')}>Воронка и прогноз<Ic n="ne" s={14} /></a>}>
        <div className="mg-tabs"><W.CalTabs kinds={TABS.map(([id, l]) => [id, l])} tab={tab} setTab={setTab} counts={TABS.reduce((a, [id, , n]) => Object.assign(a, { [id]: n }), {})} label="Какие сделки показать" /></div>
        {!list.length ? <div className="lab" style={{ marginTop: 20 }}>{tab === 'lost' ? 'Проигранных сделок нет.' : 'Сделок нет.'}</div>
          : tab === 'all' ? <div className="mg-cols">{COLS.map(([c, l]) => { const rows = list.filter((d) => (COL_OF[d.stage] || 'new') === c); return rows.length ? <section key={c} className="mg-col"><h3 className="mg-colh">{l}<span className="lab"> · {rows.length}</span></h3>{rows.map(row)}</section> : null; })}</div>
          : <div className="mg-list">{list.map(row)}</div>}
      </Modal>);
  }

  function DealModal({ id, data, me, open, close }) {
    const d = data.deals.find((x) => x.id === id);
    const DF = window.DealFunnel, DC = window.DealChat, DT = window.DealTeam;
    const f = DF ? DF.listDeals().find((x) => x.dealId === id) : null;
    const [lostOn, setLostOn] = React.useState(false);
    const [reason, setReason] = React.useState('');
    const [note, setNote] = React.useState('');
    const [err, setErr] = React.useState('');
    const [txt, setTxt] = React.useState('');
    if (!d && !f) return <Modal title="Сделка не найдена" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такой сделки нет или она удалена.</div></Modal>;
    const deal = d || { id, client: f.client, amount: f.amount, stage: 'new', prob: 0, nextAction: '' };
    const steps = DF && f ? DF.STEPS.filter((s) => f.passed[s.id] !== null) : [];
    const team = DT && DT.get ? (() => { try { return DT.get(id).roles || {}; } catch (e) { return {}; } })() : {};
    const ROLE = { manager: 'Менеджер', measurer: 'Замерщик', designer: 'Дизайнер', foreman: 'Прораб' };
    const threads = DC && !(f && f.proj) ? (DC.ensureDefaults ? DC.ensureDefaults(id, { team: ['measurer', 'designer', 'foreman'].filter((k) => team[k]) }) : DC.threads(id, 'manager')) : [];
    const ct = threads.find((t) => t.key === 'client') || threads[0] || null;
    const send = () => { if (!txt.trim() || !ct) return; DC.send(id, ct.id, { from: 'manager', name: me.name, t: txt.trim() }); if (DC.markRead) DC.markRead(id, ct.id, 'manager'); setTxt(''); };
    const lose = () => { try { MV.markLost(id, reason, me, { note, amount: deal.amount, client: deal.client, stage: stageL(deal) }); setErr(''); setLostOn(false); } catch (e) { setErr(e.message); } };
    const WHO = { client: 'Клиент', manager: 'Вы', measurer: 'Замерщик', designer: 'Дизайнер', foreman: 'Прораб' };
    const isLost = deal.stage === 'lost';
    return (
      <Modal size="wide" title={deal.client} sub={[id, deal.address || deal.prop].filter(Boolean).join(' · ')} onClose={close}
        head={<React.Fragment>{deal.phone ? <a className="lw-btn-l" href={'tel:' + String(deal.phone).replace(/[^\d+]/g, '')}>Позвонить</a> : null}<a className="lw-btn-l" href={dealWizard(id)}>Мастер сделки<Ic n="ne" s={14} /></a></React.Fragment>}>
        {steps.length > 0 && <ol className="mg-steps" aria-label="Путь сделки">{steps.map((s) => <li key={s.id} className={f.current && f.current.id === s.id ? 'is-cur' : f.passed[s.id] ? 'is-done' : ''} aria-current={f.current && f.current.id === s.id ? 'step' : undefined}>{s.short}</li>)}</ol>}
        <div className="mg-deal-top">
          <dl className="cv-meta mg-meta">
            <div><dt>Этап</dt><dd className="hot">{isLost ? 'Проиграна' : stageL(deal)}</dd></div>
            <div><dt>Сумма</dt><dd>{deal.amount > 0 ? rub(deal.amount) : 'появится, когда смета собрана'}</dd></div>
            {!isLost && deal.prob ? <div><dt>Вероятность</dt><dd>{deal.prob} %</dd></div> : null}
            {deal.source ? <div><dt>Источник</dt><dd>{deal.source}</dd></div> : null}
            {deal.tis ? <div><dt>Без движения</dt><dd className={deal.overdue ? 'hot' : ''}>{deal.tis} {plural(deal.tis, 'день', 'дня', 'дней')}</dd></div> : null}
            {deal.miss && deal.miss.length ? <div><dt>Не заполнено</dt><dd>{deal.miss.join(', ')}</dd></div> : null}
          </dl>
          <div className="card cv-dec mg-next">
            {isLost ? <React.Fragment>
              <div className="lab">Сделка проиграна {dm(deal.lost.at)} · {deal.lost.by}</div>
              <div className="t" style={{ marginTop: 6 }}>{deal.lostReasonL}{deal.lost.note ? ' — ' + deal.lost.note : ''}</div>
              <div className="cv-dbtn" style={{ display: 'flex', marginTop: 14 }}><button type="button" className="btn-l" onClick={() => MV.restoreLost(id)}>Вернуть в работу</button></div>
            </React.Fragment> : <React.Fragment>
              <div className="lab">Следующий шаг</div>
              <div className="t" style={{ marginTop: 6 }}>{deal.nextAction || (f && f.now) || '—'}</div>
              {f && f.stuck && f.stuck.length ? <ul className="mg-stuck">{f.stuck.map((s) => <li key={s.k}>{s.l}</li>)}</ul> : null}
              {lostOn ? <div className="mg-lost" style={{ marginTop: 14 }}>
                <div className="lab">Почему проиграна</div>
                <div className="mg-reasons">{MV.LOST_REASONS.map(([k, l]) => <button key={k} type="button" className={'pill-n' + (reason === k ? ' on' : '')} aria-pressed={reason === k} onClick={() => setReason(k)}>{l}</button>)}</div>
                <textarea className="cv-ta" rows={2} placeholder={reason === 'other' ? 'Опишите причину' : 'Комментарий (необязательно)'} value={note} onChange={(e) => setNote(e.target.value)} aria-label="Комментарий к проигрышу" />
                {err && <div className="att" role="alert" style={{ marginTop: 8 }}><Ic n="alert" s={16} /><span>{err}</span></div>}
                <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: 10 }}><button type="button" className="btn-l" onClick={() => { setLostOn(false); setErr(''); }}>Отмена</button><button type="button" className="btn-k" disabled={!reason} onClick={lose}>Отметить проигранной</button></div>
              </div> : <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: 14 }}>
                {deal.stage !== 'won' && <button type="button" className="btn-l" onClick={() => setLostOn(true)}>Сделка проиграна</button>}
                <a className="btn-k" href={dealCard(id)}>Вся карточка сделки</a>
              </div>}
            </React.Fragment>}
          </div>
        </div>
        <div className="cv-two lw-two mg-deal-two">
          <section className="cv-w cv-apr-w lw-rows" aria-labelledby="mg-team-h">
            <W.WHead id="mg-team-h" title="Команда сделки" />
            <div className="rows">{['manager', 'measurer', 'designer', 'foreman'].filter((k) => team[k] || k === 'manager').map((k) => (
              <div key={k} className="r lw-r"><span className="tx"><span className="t">{team[k] ? team[k].name : 'не назначен'}</span><span className="lab">{ROLE[k]}{team[k] && team[k].src ? ' · ' + team[k].src : ''}</span></span></div>))}</div>
            {f && f.proj ? <button type="button" className="cv-arrow-btn" style={{ marginTop: 12 }} onClick={() => open('proj=' + f.proj.id)}>Проект {f.proj.id}<Ic n="ne" s={14} /></button> : null}
          </section>
          <section className="cv-w mg-chat" aria-labelledby="mg-dchat-h">
            <W.WHead id="mg-dchat-h" title={ct ? 'Чат: ' + (ct.title || 'Клиент') : 'Чат с клиентом'} onOpen={() => open('chat')} label="Все чаты" />
            {f && f.proj ? <div className="lab" style={{ marginTop: 10 }}>Проект запущен — переписка в чате проекта.</div> : ct ? <React.Fragment>
              <div className="lw-msgs cv-scroll">{ct.msgs.length ? ct.msgs.slice(-12).map((m) => <div key={m.id} className={'lw-msg' + (m.from === 'manager' ? ' mine' : '')}><span className="lab">{WHO[m.from] || m.from} · {when(m.at)}</span>{m.t ? <span className="b">{m.t}</span> : null}</div>) : <div className="lab">Сообщений пока нет.</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="lab" style={{ marginTop: 10 }}>Чат не загрузился.</div>}
          </section>
        </div>
      </Modal>);
  }

  function LostModal({ open, close }) {
    const r = MV.lostReport();
    const m = MV.lostReport(t0().slice(0, 8) + '01');
    return (
      <Modal size="wide" title="Проигранные сделки" sub={r.total ? r.total + ' всего · ' + rub(r.amount) + ' · в этом месяце ' + m.total : 'проигранных нет'} onClose={close}>
        {r.by.length > 0 && <section style={{ marginTop: 18 }}><h2 className="h2" style={{ fontSize: 20 }}>Причины</h2>
          <div className="mg-why">{r.by.map((x) => <div key={x.key} className="mg-whyr"><span className="t">{x.label}</span><span className="mg-bar"><i style={{ width: Math.round(x.count / r.by[0].count * 100) + '%' }} /></span><span className="lab">{x.count} · {rub(x.amount)}</span></div>)}</div></section>}
        <div className="mg-list" style={{ marginTop: 18 }}>{r.rows.map((x) => (
          <button type="button" key={x.dealId} className="mg-deal" onClick={() => open('deal=' + x.dealId)}>
            <span className="t">{x.client || x.dealId}<span className="lab"> · {x.dealId}</span></span>
            <span className="lab">{x.reasonL}{x.note ? ' — ' + x.note : ''} · {x.stage ? 'этап «' + x.stage + '» · ' : ''}{dm(x.at)} · {x.by}</span>
            <span className="r"><b>{x.amount > 0 ? rub(x.amount) : '—'}</b></span>
          </button>))}</div>
        {!r.total && <div className="lab" style={{ marginTop: 20 }}>Здесь появятся сделки, которые вы отметите проигранными, и отчёт по причинам.</div>}
      </Modal>);
  }

  /* ───── проекты ───── */
  function ProjectsModal({ data, open, close }) {
    return (
      <Modal size="wide" title="Мои проекты" sub={data.projs.length + ' в работе'} onClose={close}>
        <div className="lw-tiles is-modal">{data.projs.map((p) => <ProjTile key={p.id} p={p} onOpen={() => open('proj=' + p.id)} />)}</div>
        {!data.projs.length && <div className="lab" style={{ marginTop: 20 }}>Запущенных проектов пока нет.</div>}
      </Modal>);
  }
  const PST = { paid: ['Оплачено', 'pill-g'], check: ['Оплачено · ждёт сверки бухгалтера', 'pill-g'], overdue: ['Просрочено', 'pill-n cv-over'], expected: ['Ожидаем оплату', 'pill-n'], planned: ['По графику', 'pill-g'] };
  function ProjectModal({ id, data, open, close }) {
    const p = data.projs.find((x) => x.id === id);
    const [sent, setSent] = React.useState('');
    const [err, setErr] = React.useState('');
    if (!p) return <Modal title="Проект не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такого проекта нет.</div></Modal>;
    const remind = (what, key) => { try { MV.remind(p.id, what); setSent(key); setErr(''); } catch (e) { setErr(e.message); } };
    const RemindBtn = ({ what, k }) => (sent === k ? <span className="pill-g sm">Напомнили</span> : <button type="button" className="lw-btn-l" onClick={() => remind(what, k)}>Напомнить клиенту</button>);
    return (
      <Modal size="wide" title={p.title} sub={[p.client, p.id].filter(Boolean).join(' · ')} onClose={close}
        head={<React.Fragment>{p.phone ? <a className="lw-btn-l" href={'tel:' + String(p.phone).replace(/[^\d+]/g, '')}>Позвонить</a> : null}{p.dealId ? <button type="button" className="lw-btn-l" onClick={() => open('deal=' + p.dealId)}>Сделка</button> : null}<button type="button" className="lw-btn-l" onClick={() => open('chat=project:' + p.id + ':')}>Чат проекта</button></React.Fragment>}>
        {err && <div className="att" role="alert" style={{ marginTop: 12 }}><Ic n="alert" s={16} /><span>{err}</span></div>}
        <FactsBand label="Проект" facts={[
          { v: p.stage, lab: p.handoff ? 'Ждёт приёма прорабом' : 'Этапов сдано ' + p.stagesDone + ' из ' + p.stagesAll },
          { v: p.pct + ' %', lab: 'Работ принято' + (p.end ? ' · сдача ' + dm(p.end) : '') },
          { v: rub(p.paid), lab: 'Оплачено из ' + rub(p.contract) },
          { v: p.overdue ? rub(p.overdue) : rub(p.expected), lab: p.overdue ? 'Просрочено' : 'Ожидаем оплату', late: p.overdue > 0 },
        ]} />
        <div className="cv-two lw-two">
          <section className="cv-w cv-apr-w lw-rows" aria-labelledby="mg-pays-h">
            <W.WHead id="mg-pays-h" title="График оплат" n={p.overdueN} />
            <div className="rows">{p.pays.map((x) => (
              <div key={x.id} className={'r lw-r' + (x.state === 'overdue' ? ' is-over' : '')}>
                <span className="tx"><span className="t">{x.title}</span><span className="lab">{rub(x.amount)}{x.due ? ' · срок ' + dm(x.due) : ''}{x.state === 'overdue' ? ' · просрочка ' + x.days + ' дн' : ''}</span></span>
                <span className={PST[x.state][1]}>{PST[x.state][0]}</span>
                {(x.state === 'overdue' || x.state === 'expected') && <span className="lw-racts"><RemindBtn what={{ kind: 'pay', title: x.title, amount: x.amount, due: x.due }} k={'pay' + x.id} /></span>}
              </div>))}</div>
            {!p.pays.length && <div className="lab" style={{ marginTop: 10 }}>Графика оплат нет.</div>}
          </section>
          <section className="cv-w cv-apr-w lw-rows" aria-labelledby="mg-acts-h">
            <W.WHead id="mg-acts-h" title="Акты у клиента" n={p.acts.length} />
            {p.acts.length ? <div className="rows">{p.acts.map((a) => (
              <div key={a.id} className="r lw-r"><span className="tx"><span className="t">{a.title}</span><span className="lab">{a.amount ? rub(a.amount) + ' · ' : ''}отправлен {dm(a.at)}</span></span><span className="lw-racts"><RemindBtn what={{ kind: 'act', title: a.title }} k={'act' + a.id} /></span></div>))}</div>
              : <div className="note-s" style={{ marginTop: 14 }}><Ic n="check" c="#2F5D3A" s={16} /><span>Все акты подписаны.</span></div>}
            <div className="lab" style={{ marginTop: 14 }}>«Напомнить клиенту» отправляет вежливое сообщение в чат клиента этого проекта. Оплату подтверждает бухгалтер.</div>
          </section>
        </div>
      </Modal>);
  }

  /* ───── деньги клиентов ───── */
  function MoneyModal({ data, open, close }) {
    const m = data.money;
    const grp = (title, rows, empty) => (
      <section style={{ marginTop: 22 }}><h2 className="h2" style={{ fontSize: 20 }}>{title}</h2>
        {rows.length ? <div className="rows mg-rows">{rows.map((x) => (
          <button type="button" key={x.id} className={'r lw-r mg-row' + (x.state === 'overdue' ? ' is-over' : '')} onClick={() => open('proj=' + x.pid)}>
            <span className="tx"><span className="t">{x.client} · {rub(x.amount)}</span><span className="lab">{x.title} · {short(x.obj)}</span></span>
            <span className="by"><b>{x.state === 'overdue' ? 'просрочка ' + x.days + ' дн' : x.state === 'check' ? 'ждёт сверки' : 'до ' + dm(x.due)}</b></span>
          </button>))}</div> : <div className="lab" style={{ marginTop: 8 }}>{empty}</div>}
      </section>);
    return (
      <Modal size="wide" title="Деньги клиентов" sub={'Просрочено ' + rub(m.overdueSum) + ' · ожидаем ' + rub(m.expectedSum) + (m.checkSum ? ' · на сверке ' + rub(m.checkSum) : '')} onClose={close}>
        {grp('Просрочено', m.overdue, 'Просроченных счетов нет.')}
        {grp('Ожидаем оплату', m.expected, 'Выставленных счетов до срока нет.')}
        {grp('Клиент оплатил, ждёт сверки бухгалтера', m.check, 'Все оплаты сверены.')}
      </Modal>);
  }

  /* ───── чаты: сделки и проекты ───── */
  const WHO = { client: 'Клиент', manager: 'Вы', measurer: 'Замерщик', designer: 'Дизайнер', foreman: 'Прораб', procurement: 'Закупки', accountant: 'Бухгалтерия', executor: 'Мастер', super: 'Руководитель', marketing: 'Маркетолог' };
  function ChatModal({ data, sel, open, close, me }) {
    const [src0, pid0, tid0] = (sel || '').split(':');
    const cur = data.chats.find((c) => c.src === src0 && c.pid === pid0 && (!tid0 || c.id === tid0)) || data.chats[0] || null;
    const [txt, setTxt] = React.useState('');
    const DC = window.DealChat;
    const thread = !cur ? null : cur.src === 'project' ? ((P.get(cur.pid) || {}).chat || { threads: [] }).threads.find((t) => t.id === cur.id) : (DC ? DC.load(cur.pid).threads.find((t) => t.id === cur.id) : null);
    React.useEffect(() => { if (cur && cur.src === 'deal' && DC && DC.markRead && cur.isNew) DC.markRead(cur.pid, cur.id, 'manager'); }, [cur && cur.pid, cur && cur.id]);
    const send = () => {
      if (!txt.trim() || !thread) return;
      if (cur.src === 'project') P.chatSend(cur.pid, thread.id, { from: 'manager', t: txt.trim() }); else DC.send(cur.pid, thread.id, { from: 'manager', name: me.name, t: txt.trim() });
      setTxt('');
    };
    return (
      <Modal size="wide" title="Чаты" sub="сделки и проекты · клиенты и команда" onClose={close}>
        <div className="lw-chat">
          <div className="lw-chl">{data.chats.map((c) => (
            <button type="button" key={c.src + c.pid + c.id} className={'lw-chr' + (cur && c.src === cur.src && c.pid === cur.pid && c.id === cur.id ? ' on' : '') + (c.isNew ? ' is-new' : '')} onClick={() => open('chat=' + c.src + ':' + 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 !== 'manager').map((m) => WHO[m] || m).join(', ')}</span>
              {cur.src === 'deal' ? <button type="button" className="cv-arrow-btn" onClick={() => open('deal=' + cur.pid)}>Сделка<Ic n="ne" s={14} /></button> : <button type="button" className="cv-arrow-btn" onClick={() => open('proj=' + 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 === 'manager' ? ' mine' : '')}><span className="lab">{WHO[m.from] || m.from} · {when(m.at)}</span>
                {m.t ? <span className="b">{m.t}</span> : null}{m.media && m.media.url ? <img src={m.media.url} alt={m.media.name || ''} /> : 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>);
  }

  /* ───── решения из «Мои задачи»: те же функции, что блоки прежнего кабинета (lk-common.jsx) ───── */
  const DEC_T = { rpl: 'Замена материала', xp: 'Цены мастеров выше цены компании', spec: 'Правки клиента по спецификации', pay: 'Оплата открывает этап' };
  function DecModal({ arg, me, close }) {
    const [kind, pid] = arg.split(':');
    const [why, setWhy] = React.useState({});
    const [rej, setRej] = React.useState(null);
    const [err, setErr] = React.useState('');
    const [f, setF] = React.useState({});
    const actor = { role: 'manager', name: me.name };
    const p = pid ? P.get(pid) : null;
    const run = (fn) => { try { fn(); setErr(''); setRej(null); } catch (e) { setErr(e.message); } };
    let cards = [];
    if (kind === 'rpl') cards = (P.views.pendingReplacements ? P.views.pendingReplacements('manager') : []).filter((r) => !pid || r.pid === pid).map((r) => (
      <div key={r.id} className="card cv-dec">
        <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{r.prop.name}</div><div className="lab" style={{ marginTop: 3 }}>вместо «{r.orig.name}» · {r.room} · {r.reasonL}</div></div></div>
        <dl className="cv-meta"><div><dt>Было</dt><dd>{r.orig.supplier} · {rub(r.orig.price)} · {r.orig.lead} дн</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 ? 'hot' : ''}>{r.priceDelta === 0 ? 'без изменений' : (r.priceDelta > 0 ? '+' : '') + rub(r.priceDelta) + (r.priceDelta > 0 ? ' · потребует согласия клиента' : '')}</dd></div><div><dt>Срок</dt><dd>{r.leadDelta <= 0 ? 'не хуже' : '+' + r.leadDelta + ' дн'}</dd></div></dl>
        {r.note ? <div className="lab" style={{ marginTop: 8 }}>{r.note}</div> : null}
        <Decide k={r.id} rej={rej} setRej={setRej} why={why} setWhy={setWhy} onYes={() => run(() => P.decideReplacement(r.pid, r.id, 'approve', '', actor))} onNo={() => run(() => P.decideReplacement(r.pid, r.id, 'reject', (why[r.id] || '').trim(), actor))} noL="Отклонить" />
      </div>));
    if (kind === 'xp') { const EP = window.ExecPrices; const ERR = { rights: 'Решать может менеджер или руководитель', reason: 'Напишите причину отказа', state: 'Предложение уже решено или отозвано' };
      const key = (o) => o.pid + '~' + o.executorId + '~' + o.nodeId;
      const decide = (o, d) => { const r = EP.decideOffer(o.executorId, o.pid, o.nodeId, d, { by: me.name, role: 'manager', reason: why[key(o)] || '' }); setErr(r.ok ? '' : ERR[r.error] || 'Не получилось'); if (r.ok) setRej(null); };
      cards = (EP && EP.projectOffers ? P.list().filter((x) => !pid || x.id === pid).flatMap((x) => EP.projectOffers(x)).filter((o) => o.level === 'manager') : []).map((o) => (
        <div key={key(o)} className="card cv-dec">
          <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{o.name}</div><div className="lab" style={{ marginTop: 3 }}>{[o.obj, o.executorName].filter(Boolean).join(' · ')}</div></div></div>
          <dl className="cv-meta"><div><dt>Предлагает</dt><dd className="hot">{rub(o.price)}</dd></div><div><dt>Сейчас</dt><dd>{rub(o.current)}</dd></div><div><dt>Цена компании</dt><dd>{o.norm != null ? rub(o.norm) : '—'}</dd></div></dl>
          <Decide k={key(o)} rej={rej} setRej={setRej} why={why} setWhy={setWhy} onYes={() => decide(o, 'approve')} onNo={() => decide(o, 'reject')} noL="Отказать" />
        </div>)); }
    if (kind === 'spec') cards = (P.views.specRevisions ? P.views.specRevisions('manager') : []).filter((r) => !pid || r.pid === pid).map((r) => {
      const v = f[r.id] || { name: r.name, price: String(r.price || ''), note: '' };
      const set = (k, x) => setF(Object.assign({}, f, { [r.id]: Object.assign({}, v, { [k]: x }) }));
      return (
        <div key={r.id} className="card cv-dec">
          <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{r.name}</div><div className="lab" style={{ marginTop: 3 }}>{[r.obj, r.stageName].filter(Boolean).join(' · ')}</div></div></div>
          {r.comment ? <div className="lab" style={{ marginTop: 8 }}>Клиент: «{r.comment}»</div> : null}
          <div className="mg-form"><input value={v.name} onChange={(e) => set('name', e.target.value)} aria-label="Название" placeholder="Название" /><input value={v.price} onChange={(e) => set('price', e.target.value)} aria-label="Цена" placeholder="Цена, ₽" inputMode="numeric" />
            <textarea className="cv-ta" rows={2} value={v.note} onChange={(e) => set('note', e.target.value)} placeholder="Что изменили — клиент увидит" aria-label="Что изменили" /></div>
          <div className="cv-dbtn" style={{ display: 'flex', marginTop: 10 }}><button type="button" className="btn-k" disabled={!v.note.trim()} onClick={() => run(() => P.resubmitMaterial(r.pid, r.matId, { name: v.name, price: v.price, note: v.note }, actor))}>Отправить клиенту</button></div>
        </div>); });
    if (kind === 'pay') cards = (P.views.payLocks ? P.views.payLocks(pid || undefined) : []).map((r) => (
      <div key={r.pid + r.paymentId} className="card cv-dec">
        <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{r.title}</div><div className="lab" style={{ marginTop: 3 }}>{r.obj}</div></div></div>
        <dl className="cv-meta"><div><dt>Сумма</dt><dd className="hot">{rub(r.amount)}</dd></div><div><dt>Статус</dt><dd>{r.waitingFor === 'confirm' ? 'клиент оплатил, подтверждает бухгалтер' : 'ждём оплату клиента'}</dd></div>{r.blocked ? <div><dt>Держит</dt><dd>{r.blocked} {plural(r.blocked, 'работу', 'работы', 'работ')}</dd></div> : null}</dl>
        {r.waitingFor !== 'confirm' && <div className="cv-dbtn" style={{ display: 'flex', marginTop: 14 }}><button type="button" className="btn-l" onClick={() => run(() => MV.remind(r.pid, { kind: 'pay', title: r.title, amount: r.amount }))}>Напомнить клиенту</button></div>}
      </div>));
    return (
      <Modal size="wide" title={DEC_T[kind] || 'Решение'} sub={p ? p.title : ''} onClose={close}>
        {err && <div className="att" role="alert" style={{ marginTop: 12 }}><Ic n="alert" s={16} /><span>{err}</span></div>}
        {cards.length ? <div className="cv-cards" style={{ marginTop: 18 }}>{cards}</div> : <div className="note-s" style={{ marginTop: 18 }}><Ic n="check" c="#2F5D3A" s={16} /><span>Решено — здесь больше ничего не ждёт вас.</span></div>}
      </Modal>);
  }
  function Decide({ k, rej, setRej, why, setWhy, onYes, onNo, noL }) {
    return (
      <div style={{ marginTop: 14 }}>
        {rej === k && <textarea className="cv-ta" rows={2} autoFocus placeholder="Почему — увидят в истории" aria-label="Причина" value={why[k] || ''} onChange={(e) => setWhy(Object.assign({}, why, { [k]: e.target.value }))} onKeyDown={(e) => { if (e.key === 'Escape') setRej(null); }} />}
        <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: rej === k ? 10 : 0 }}>
          {rej === k ? <React.Fragment><button type="button" className="btn-l" onClick={() => setRej(null)}>Отмена</button><button type="button" className="btn-k" disabled={!(why[k] || '').trim()} onClick={onNo}>Отправить</button></React.Fragment>
            : <React.Fragment><button type="button" className="btn-l" onClick={() => setRej(k)}>{noL}</button><button type="button" className="btn-k" onClick={onYes}>Согласовать</button></React.Fragment>}
        </div>
      </div>);
  }

  /* ───── данные и маршрутизация окон ───── */
  function collect() {
    const ests = MV.loadEstimates();
    const deals = MV.deals(ests);
    const DF = window.DealFunnel, T = window.DealTeam;
    const lost = MV.lostMap();
    const fd = DF ? DF.listDeals() : [];
    const gone = (m) => !!(m && m.id && T && T.isGone && T.isGone(m.id));
    const leads = T ? fd.filter((d) => !d.proj && !lost[d.dealId] && (!(d.intake.team && d.intake.team.manager && d.intake.team.manager.name) || gone(d.intake.team.manager))) : [];
    const wait = (P && P.views.clientWaitingAll ? P.views.clientWaitingAll().items : []).concat(window.DealSignals ? window.DealSignals.all().filter((x) => !lost[x.dealId]) : [])
      .sort((a, b) => (b.overdue - a.overdue) || String(a.at).localeCompare(String(b.at)));
    return { deals, queue: MV.queue(deals, ests, t0()), projs: MV.projects(t0()), money: MV.money(t0()), chats: MV.chats(), wait, leads };
  }

  function App() {
    useStore();
    const me = useMe();
    const [modal, open, close] = useHash();
    if (!P || !MV) return <div className="cv-page"><div className="empty">Кабинет не загрузился. Обновите страницу.</div></div>;
    const data = collect();
    if (me.deny) return <W.Denied why={me.deny} />;
    const arg = (k) => (modal.indexOf(k + '=') === 0 ? modal.slice(k.length + 1) : null);
    const dealsWin = modal === 'deals' || !!arg('deals') || !!arg('deal');
    const chatWin = modal === 'chat' || arg('chat') != null;
    return (
      <React.Fragment>
        {me.home ? <Home me={me} data={data} open={open} /> : <W.Denied title="Главная закрыта" why="Главная кабинета не входит в выданный доступ — открытые вам разделы работают по своим ссылкам." />}
        {dealsWin && !me.deals && <W.Closed what="Раздел «Сделки»" onClose={close} />}
        {chatWin && !me.chat && <W.Closed what="Раздел «Чаты»" onClose={close} />}
        {(modal === 'deals' || arg('deals')) && me.deals && <DealsModal key={modal} data={data} sel={arg('deals')} open={open} close={close} />}
        {arg('deal') && me.deals && <DealModal key={arg('deal')} id={arg('deal')} data={data} me={me} open={open} close={close} />}
        {modal === 'lost' && me.deals && <LostModal open={open} close={close} />}
        {modal === 'projects' && me.home && <ProjectsModal data={data} open={open} close={close} />}
        {arg('proj') && me.home && <ProjectModal key={arg('proj')} id={arg('proj')} data={data} open={open} close={close} />}
        {modal === 'money' && me.home && <MoneyModal data={data} open={open} close={close} />}
        {chatWin && me.chat && <ChatModal data={data} sel={arg('chat')} open={open} close={close} me={me} />}
        {arg('dec') && me.home && <DecModal key={arg('dec')} arg={arg('dec')} me={me} close={close} />}
      </React.Fragment>);
  }

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