/* executor-v2-app.jsx — кабинет мастера в виде кабинета клиента (KSH-521/522/523): шапка, главная по всем объектам, разделы — окнами по #hash.
   Роман, 05.11.2026: вся деятельность мастера сгруппирована по проектам; на главной — только важные процессы и плитки объектов.
   Главная: «Новые наряды» по всем объектам (согласие до старта, вопрос уходит прорабу в чат наряда) + ExecHome (executor-home.jsx: деньги,
   «Сейчас важно», «Мои задачи» с графиком, плитки, отпуск) + чаты и заработок.
   Окна: #proj=<id> — проект: моя смета (доля, принято, удержано, к выплате), мои работы по этапам (кто и когда принял, фото результата),
   цены проекта с предложением новой цены, документы моих работ, к выплате, журнал, чаты проекта;
   #money — заработок и активность по проектам и месяцам (только цифры, без рейтинга); #chat[=<pid>:<tid>] — чаты моих работ и проектов.
   Данные — ExecutorViews (executor-views.js). Действия — ProjectStore.agreeTask / chatOpenForTask / chatSend, ExecPrices.offerPrice.
   Карточка задания (executor-task.html), «Мой прайс», «Документы» и выезды замерщика остаются прежними страницами. */
(function () {
  'use strict';
  const P = window.ProjectStore, V = window.ExecutorViews, EP = window.ExecPrices, W = window.LKW, A = window.AccessStore;
  const { Ic, Header, Modal, FactsBand, RowsW, ChatsW, 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) : '');

  function useMe() {
    const acc = A && A.currentAccount ? A.currentAccount() : null;
    const st = acc && window.StaffStore && window.StaffStore.get ? window.StaffStore.get(acc.profileId) : null;
    // доступ — как в прежних страницах (lk-bootstrap): неактивный аккаунт и чужая роль не видят ничего, выключенный раздел — не открывается
    const zone = (z) => !!(acc && A.zoneAllowed && A.zoneAllowed(acc, z));
    return { id: acc ? acc.profileId : null, name: (st && st.name) || (acc && acc.name) || 'Мастер', trade: acc ? acc.trade || '' : '',
      status: acc ? acc.status || 'active' : '', role: acc ? acc.role : '', money: zone('estimate'), chat: zone('chat') };
  }
  const DENY = { revoked: 'Доступ закрыт — вы больше не работаете с кабинетом.', blocked: 'Учётная запись заблокирована.', invited: 'Приглашение ещё не активировано.', paused: 'Учётная запись на паузе.' };
  const denied = (me) => (!me.id ? '' : me.role && me.role !== 'executor' && me.role !== 'super' ? 'Эта ссылка открывает кабинет другой роли.' : me.status !== 'active' ? DENY[me.status] || 'Учётная запись не активна.' : '');
  const EVS = ['storage', 'obitel:sync', 'obitel:exec-prices', 'obitel:staff'];
  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' }); };
  // личность (?t=, ?user=) переносится на прежние страницы
  const keep = (page, extra) => { const q = new URLSearchParams(window.location.search); Object.entries(extra || {}).forEach(([k, v]) => q.set(k, v)); const s = q.toString(); return page + (s ? '?' + s : ''); };
  const taskUrl = (id) => keep('executor-task.html', { task: id });

  const NAV = [['', 'Главная'], ['money', 'Деньги'], ['chat', 'Чаты']];
  const more = () => [[keep('executor-tasks.html', { tab: 'tasks' }), 'Все задачи'], [keep('executor-price.html'), 'Мой прайс'], [keep('executor-docs.html'), 'Документы'], ['tools.html?role=executor', 'Инструменты'], [keep('executor-profile.html'), 'Профиль']];
  const WS = { done: 'pill-g', review: 'pill-n', returned: 'pill-n cv-over', progress: 'pill-n', todo: 'pill-g' };

  /* ───── новые наряды по всем объектам (KSH-519): «Согласен» или «Есть вопросы» — вопрос уходит прорабу в чат этой работы ───── */
  function OrderCard({ w, me, open }) {
    const [asking, setAsking] = React.useState(false);
    const [q, setQ] = React.useState('');
    const [err, setErr] = React.useState('');
    const actor = { role: 'executor', name: me.name };
    const run = (fn) => { try { fn(); setErr(''); } catch (e) { setErr(e.message); } };
    const ask = () => run(() => {
      P.agreeTask(w.pid, w.id, me.id, 'question', actor, q);
      const th = P.chatOpenForTask(w.pid, w.id, actor);
      P.chatSend(w.pid, th.id, { from: 'executor', t: 'Вопрос по наряду «' + w.name + '»: ' + q.trim() });
      setAsking(false); setQ('');
    });
    return (
      <article className="card xv-ord">
        <div className="xv-ord-h"><span className="t">{w.name}</span><span className="lab">{short(w.obj)} · {w.stageName}</span></div>
        <dl className="cv-meta">
          <div><dt>Объём</dt><dd>{w.qty}{w.unit ? ' ' + w.unit : ''}</dd></div>
          <div><dt>Ваша цена</dt><dd>{w.noPrice ? 'нет цены' : rub(w.unitPrice) + (w.unit ? ' / ' + w.unit : '')}</dd></div>
          <div><dt>Сроки</dt><dd>{dm(w.start)}{w.end && w.end !== w.start ? ' — ' + dm(w.end) : ''}</dd></div>
          <div><dt>Сумма</dt><dd className="hot">{rub(w.share)}</dd></div>
        </dl>
        {w.agree === 'question' && w.question ? <p className="lab xv-q">Ваш вопрос прорабу: «{w.question}»</p> : null}
        {asking
          ? <div className="xv-ask"><textarea className="cv-ta" rows={2} autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Что уточнить у прораба: объём, сроки, материалы, доступ на объект" aria-label="Вопрос прорабу" />
              <div className="cv-dbtn"><button type="button" className="btn-l" onClick={() => { setAsking(false); setQ(''); }}>Отмена</button><button type="button" className="btn-k" disabled={!q.trim()} onClick={ask}>Отправить прорабу</button></div></div>
          : <div className="cv-dbtn">
              {w.agree === 'question' && w.threadId && me.chat ? <button type="button" className="btn-l" onClick={() => open('chat=' + w.pid + ':' + w.threadId)}>Открыть чат</button> : null}
              <button type="button" className="btn-l" onClick={() => setAsking(true)}>{w.agree === 'question' ? 'Ещё вопрос' : 'Есть вопросы'}</button>
              <button type="button" className="btn-k" onClick={() => run(() => P.agreeTask(w.pid, w.id, me.id, 'agreed', actor))}>Согласен</button></div>}
        {err && <div className="att" role="alert"><Ic n="alert" s={16} /><span>{err}</span></div>}
      </article>);
  }
  function Orders({ me, rows, open }) {
    if (!rows.length) return null;
    return (
      <section className="xv-ords" aria-label="Новые наряды">
        <div className="xv-ords-h"><div><h2 className="xh-h2">Новые наряды · {rows.length}</h2>
          <p className="lab">Прораб выдал вам работу. Проверьте объём, цену и сроки: начать можно только после вашего согласия.</p></div>
          <div className="xv-sum"><span className="lab">На сумму</span><b>{rub(rows.reduce((a, w) => a + (+w.share || 0), 0))}</b></div></div>
        <div className="xv-ords-g">{rows.map((w) => <OrderCard key={w.pid + w.id} w={w} me={me} open={open} />)}</div>
      </section>);
  }

  /* ───── главная ───── */
  function Home({ me, data, open }) {
    const { orders, chats, act } = data;
    const first = (me.name || '').split(' ')[0];
    const unread = chats.filter((c) => c.isNew).length;
    const extra = !me.money && !me.chat ? null : (
      <section className="cv-two lw-two xv-two">
        {me.money && <RowsW id="xv-money-h" title="Заработок по месяцам" n={0} className="is-grow"
          rows={act.months.slice(0, 4).map((m) => ({ key: m.key, ic: 'wallet', title: cap(m.label) + ' · ' + rub(m.pay), sub: m.n + ' ' + plural(m.n, 'работа принята', 'работы приняты', 'работ принято'), onClick: () => open('money') }))}
          empty="Принятых работ пока нет — заработок появится после приёмки." onOpen={() => open('money')} text="Деньги и активность" />}
        {me.chat && <ChatsW chats={chats.slice(0, 5).map((c) => Object.assign({}, c, { who: c.who ? (c.who === 'executor' ? 'Вы' : WHO[c.who] || c.who) : '', when: when(c.at) }))} onOpen={() => open('chat')} onPick={(c) => open('chat=' + c.pid + ':' + c.id)}
          empty="Чатов пока нет. Вопрос по работе — кнопка «Написать прорабу» в окне проекта." />}
      </section>);
    return (
      <div className="cv-page lw-page">
        <Header nav={NAV.filter(([id]) => (id !== 'money' || me.money) && (id !== 'chat' || me.chat))} 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()} · {data.projs.length} {plural(data.projs.length, 'объект', 'объекта', 'объектов')} в работе</div><h1 className="cv-hello">{greeting()}{first ? ', ' + first : ''}</h1></div>
            <div className="cv-hero-act">
              {unread && me.chat ? <button type="button" className="btn-l" onClick={() => open('chat')}>Новые сообщения · {unread}</button> : null}
              {orders.length ? <a className="btn-k" href="#xv-ords" onClick={(e) => { e.preventDefault(); const el = document.querySelector('.xv-ords'); if (el) el.scrollIntoView({ behavior: 'smooth' }); }}>Новые наряды · {orders.length}</a>
                : <span className="pill-g"><span className="dot" />Наряды подтверждены</span>}
            </div>
          </section>
          {window.ExecHome ? <window.ExecHome me={me.id} onProject={(pid) => open('proj=' + pid)} orders={<Orders me={me} rows={orders} open={open} />} extra={extra} /> : null}
        </main>
      </div>);
  }

  /* ───── окно проекта ───── */
  const OFFER_ERR = { price: 'Только число', rights: 'Предложить цену может только сам исполнитель', work: 'Этой работы у вас в проекте нет', args: 'Проект не найден' };
  function PriceRow({ r, me, pid }) {
    const [edit, setEdit] = React.useState(false);
    const [val, setVal] = React.useState('');
    const [err, setErr] = React.useState('');
    const send = () => { const res = EP.offerPrice(me.id, pid, r.nodeId, val); if (!res.ok) { setErr(OFFER_ERR[res.error] || 'Не получилось'); return; } setErr(''); setVal(''); setEdit(false); };
    const o = r.offer;
    return (
      <div className="r lw-r">
        <span className="tx"><span className="t">{r.name}</span>
          <span className="lab">{r.qty}{r.unit ? ' ' + r.unit : ''}{r.norm != null ? ' · цена компании ' + rub(r.norm) : ''}
            {o && o.state === 'pending' ? ' · на согласовании ' + rub(o.price) + ', решает ' + o.levelL : o && o.state === 'rejected' ? ' · не согласовали ' + rub(o.price) + (o.reason ? ': ' + o.reason : '') : ''}</span>
          {edit && <span className="xv-offer"><input value={val} autoFocus inputMode="decimal" placeholder="Новая цена, ₽" aria-label={'Новая цена: ' + r.name} onChange={(e) => { setVal(e.target.value); setErr(''); }} onKeyDown={(e) => { if (e.key === 'Enter') send(); if (e.key === 'Escape') setEdit(false); }} />
            <button type="button" className="lw-btn-k" disabled={!val.trim()} onClick={send}>Предложить</button><button type="button" className="lw-btn-l" onClick={() => setEdit(false)}>Отмена</button>
            {err && <span className="xv-err">{err}</span>}</span>}
        </span>
        <span className="by"><b>{r.price != null ? rub(r.price) : r.norm != null ? rub(r.norm) : '—'}</b><span className="lab">{r.price != null ? 'моя цена' : 'как у компании'}</span></span>
        {!edit && <span className="lw-racts">{o && o.state === 'pending'
          ? <button type="button" className="lw-btn-l" onClick={() => EP.offerPrice(me.id, pid, r.nodeId, '')}>Отозвать</button>
          : <button type="button" className="lw-btn-l" onClick={() => setEdit(true)} aria-label={'Изменить цену: ' + r.name}>Изменить</button>}</span>}
      </div>);
  }
  const WHO = { client: 'Клиент', manager: 'Менеджер', foreman: 'Прораб', procurement: 'Закупки', accountant: 'Бухгалтерия', executor: 'Вы', super: 'Руководитель', designer: 'Дизайнер', marketing: 'Маркетинг' };
  const DOC_FROM = { mine: 'мой результат', foreman: 'от прораба', designer: 'от дизайнера', client: 'от клиента' };
  function ProjectModal({ id, me, open, close }) {
    const x = V.project(me.id, id, t0());
    const [tab, setTab] = React.useState('works');
    const [err, setErr] = React.useState('');
    if (!x) return <Modal title="Проект не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Этого объекта нет среди ваших.</div></Modal>;
    const t = x.totals;
    const actor = { role: 'executor', name: me.name };
    const write = (taskId) => { try { const th = P.chatOpenForTask(x.id, taskId, actor); setErr(''); open('chat=' + x.id + ':' + th.id); } catch (e) { setErr(e.message); } };
    const TABS = [['works', 'Мои работы'], ['prices', 'Цены проекта'], ['docs', 'Документы'], ['pay', 'К выплате'], ['log', 'Журнал']];
    const counts = { works: t.works, prices: x.pendingPrices, docs: x.docs.length, pay: x.payRows.length, log: x.log.length };
    return (
      <Modal size="wide" title={x.title} sub={[x.address !== x.title ? x.address : '', x.id].filter(Boolean).join(' · ')} onClose={close}
        head={!me.chat ? null : <button type="button" className="lw-btn-l" onClick={() => open('chat=' + x.id + ':')}>Чаты проекта{x.chats.filter((c) => c.isNew).length ? ' · ' + x.chats.filter((c) => c.isNew).length : ''}</button>}>
        {err && <div className="att" role="alert" style={{ marginTop: 12 }}><Ic n="alert" s={16} /><span>{err}</span></div>}
        <FactsBand label="Моя смета на объекте" facts={[
          { v: rub(t.share), lab: 'Моя сумма · ' + t.works + ' ' + plural(t.works, 'работа', 'работы', 'работ') },
          { v: t.accepted + ' из ' + t.works, lab: 'Принято работ' },
          { v: rub(t.pay), lab: 'К выплате' },
          { v: t.hold ? rub(t.hold) : '—', lab: 'Удержано' },
        ]} />
        <div className="mg-tabs"><W.CalTabs kinds={TABS} tab={tab} setTab={setTab} counts={counts} label="Раздел проекта" /></div>

        {tab === 'works' && (x.phases.length ? x.phases.map((ph) => (
          <section key={ph.idx} className="xv-ph"><h3 className="xv-h3">{ph.name}<span className="lab"> · {rub(ph.share)}</span></h3>
            {ph.stages.map((s) => (
              <div key={s.id} className="xv-stage"><div className="lab xv-sh">{s.name} · {rub(s.share)}</div>
                <div className="rows mg-rows">{s.works.map((w) => (
                  <div key={w.id} className={'r lw-r' + (w.late || w.status === 'returned' ? ' is-over' : '')}>
                    {w.cover ? <img className="xv-cover" src={w.cover} alt="" /> : null}
                    <span className="tx"><a className="t" href={taskUrl(w.id)}>{w.name}</a>
                      <span className="lab">{w.qty}{w.unit ? ' ' + w.unit : ''} × {w.noPrice ? 'нет цены' : rub(w.unitPrice)} = {rub(w.share)}{w.start ? ' · ' + dm(w.start) + (w.end && w.end !== w.start ? '–' + dm(w.end) : '') : ''}
                        {w.accepted ? ' · принял ' + w.accepted.by + ' ' + dm(w.accepted.at) : ''}{w.photos ? ' · фото: ' + w.photos : ''}{w.remark ? ' · замечание: ' + w.remark : ''}</span></span>
                    <span className={w.agree ? 'pill-n' : WS[w.status] || 'pill-g'}>{w.late && w.status !== 'returned' ? 'Просрочено' : w.stL}</span>
                    {me.chat && <span className="lw-racts"><button type="button" className="lw-btn-l" onClick={() => write(w.id)}>Написать прорабу</button></span>}
                  </div>))}</div>
              </div>))}
          </section>)) : <div className="lab" style={{ marginTop: 16 }}>Работ пока нет — их назначит прораб.</div>)}

        {tab === 'prices' && <section style={{ marginTop: 16 }}>
          <p className="lab">Цена действует только на этот проект. Новую цену согласует прораб, а выше цены компании — менеджер или руководитель. Пока не согласовали, действует прежняя.</p>
          {x.prices.length ? <div className="rows mg-rows">{x.prices.map((r) => <PriceRow key={r.nodeId} r={r} me={me} pid={x.id} />)}</div> : <div className="lab" style={{ marginTop: 10 }}>Работ с ценой пока нет.</div>}
        </section>}

        {tab === 'docs' && <section style={{ marginTop: 16 }}>
          {x.docs.length ? <div className="rows mg-rows">{x.docs.map((d) => (
            <div key={d.id} className="r lw-r"><span className="tx">{d.url ? <a className="t" href={d.url} target="_blank" rel="noopener">{d.name}</a> : <span className="t">{d.name}</span>}
              <span className="lab">{d.work} · {DOC_FROM[d.from] || (WHO[d.from] || d.from).toLowerCase()}{d.at ? ' · ' + dm(d.at) : ''}</span></span></div>))}</div>
            : <div className="lab">Файлов и замечаний по вашим работам пока нет. Чертежи и схемы от прораба и дизайнера появятся здесь.</div>}
        </section>}

        {tab === 'pay' && <section style={{ marginTop: 16 }}>
          {x.payRows.length ? <div className="rows mg-rows">{x.payRows.map((r) => (
            <div key={r.id} className="r lw-r"><span className="tx"><span className="t">{r.name}</span><span className="lab">{r.stage}{r.at ? ' · принял ' + r.by + ' ' + dm(r.at) : ''}{r.hold ? ' · удержано ' + rub(r.hold) : ''}</span></span>
              <span className="by"><b>{rub(r.pay)}</b></span></div>))}</div> : <div className="lab">К выплате появится после приёмки работ прорабом.</div>}
          <p className="lab" style={{ marginTop: 12 }}>К выплате — принятые работы за вычетом удержаний. Учёт выплат появится в системе позже.</p>
        </section>}

        {tab === 'log' && <section style={{ marginTop: 16 }}>
          {x.log.length ? <div className="rows mg-rows">{x.log.map((e) => (
            <div key={e.id} className={'r lw-r' + (e.kind === 'returned' ? ' is-over' : '')}><span className="tx"><span className="t">{e.l}: {e.work}</span><span className="lab">{when(e.at)}{e.by ? ' · ' + e.by : ''}{e.note ? ' · ' + e.note : ''}</span></span></div>))}</div>
            : <div className="lab">Пока ничего не происходило.</div>}
        </section>}
      </Modal>);
  }

  /* ───── деньги и активность (KSH-522): только цифры, без оценки ───── */
  function MoneyModal({ me, data, open, close }) {
    const a = data.act; const z = a.total;
    return (
      <Modal size="wide" title="Деньги и активность" sub={'Всего по нарядам ' + rub(z.share)} onClose={close}>
        <FactsBand label="Итого по всем объектам" facts={[
          { v: rub(z.pay), lab: 'К выплате · ' + z.accepted + ' ' + plural(z.accepted, 'работа принята', 'работы приняты', 'работ принято') },
          { v: z.hold ? rub(z.hold) : '—', lab: 'Удержано' },
          { v: z.returns + '', lab: 'Возвратов на доработку', late: z.returns > 0 },
          { v: z.onTime + ' / ' + z.late, lab: 'Сдано в срок / с опозданием' },
          { v: z.open + '', lab: 'Работ впереди' },
        ]} />
        <div className="cv-two lw-two">
          <section className="cv-w cv-apr-w lw-rows is-grow" aria-labelledby="xv-pr-h">
            <W.WHead id="xv-pr-h" title="По объектам" />
            <div className="rows">{a.rows.map((r) => (
              <button type="button" key={r.pid} className="r lw-r" onClick={() => open('proj=' + r.pid)}>
                <span className="tx"><span className="t">{short(r.obj)}</span><span className="lab">принято {r.accepted} из {r.works} · возвратов {r.returns} · в срок {r.onTime}{r.late ? ', с опозданием ' + r.late : ''}</span></span>
                <span className="by"><b>{rub(r.pay)}</b><span className="lab">из {rub(r.share)}</span></span></button>))}</div>
          </section>
          <section className="cv-w cv-apr-w lw-rows" aria-labelledby="xv-mo-h">
            <W.WHead id="xv-mo-h" title="По месяцам" />
            {a.months.length ? <div className="rows">{a.months.map((m) => (
              <div key={m.key} className="r lw-r"><span className="tx"><span className="t">{cap(m.label)}</span><span className="lab">{m.n} {plural(m.n, 'работа принята', 'работы приняты', 'работ принято')}</span></span><span className="by"><b>{rub(m.pay)}</b></span></div>))}</div>
              : <div className="lab" style={{ marginTop: 10 }}>Принятых работ пока нет.</div>}
          </section>
        </div>
        <section style={{ marginTop: 24 }}><h2 className="h2" style={{ fontSize: 20 }}>Журнал</h2>
          {a.log.length ? <div className="rows mg-rows">{a.log.map((e) => (
            <a key={e.id} className={'r lw-r' + (e.kind === 'returned' ? ' is-over' : '')} href={taskUrl(e.taskId)}><span className="tx"><span className="t">{e.l}: {e.work}</span>
              <span className="lab">{short(e.obj)} · {when(e.at)}{e.by ? ' · ' + e.by : ''}{e.note ? ' · ' + e.note : ''}</span></span></a>))}</div>
            : <div className="lab" style={{ marginTop: 8 }}>Здесь будет видно, что вы взяли в работу, сдали, что вернули и приняли.</div>}
        </section>
        <p className="lab" style={{ marginTop: 16 }}>К выплате — принятые работы за вычетом удержаний. Учёт выплат появится в системе позже.</p>
      </Modal>);
  }

  /* ───── чаты: работы и общие чаты моих проектов ───── */
  function ChatModal({ me, data, sel, open, close }) {
    const [pid0, tid0] = (sel || '').split(':');
    const list = data.chats;
    const cur = list.find((c) => c.pid === pid0 && (!tid0 || c.id === tid0)) || (pid0 ? null : list[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: 'executor', t: txt.trim() }); setTxt(''); };
    const shown = pid0 && !tid0 ? list.filter((c) => c.pid === pid0) : list;
    return (
      <Modal size="wide" title="Чаты" sub="ваши работы и объекты" onClose={close}>
        <div className="lw-chat">
          <div className="lw-chl">{shown.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=' + c.pid + ':' + c.id)}>
              <span className="t">{c.title}<span className="lab"> · {short(c.obj)}</span></span><span className="lab">{c.who ? (WHO[c.who] || c.who) + ': ' : ''}{c.text}</span><span className="tm lab">{when(c.at)}</span></button>))}
            {pid0 && !tid0 && shown.length < list.length ? <button type="button" className="cv-arrow-btn" onClick={() => open('chat')}>Все чаты</button> : null}</div>
          <div className="lw-chv">{thread ? <React.Fragment>
            <div className="lw-chh"><b>{cur.title}</b><span className="lab">{short(cur.obj)} · {thread.members.filter((m) => m !== 'executor').map((m) => WHO[m] || m).join(', ')}</span>
              {cur.taskId ? <a className="cv-arrow-btn" href={taskUrl(cur.taskId)}>Карточка работы<Ic n="ne" s={14} /></a> : <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 === 'executor' ? ' mine' : '')}><span className="lab">{m.from === 'executor' ? 'Вы' : 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">{list.length ? 'Выберите чат слева.' : 'Чатов пока нет. Вопрос по работе — «Написать прорабу» в окне проекта: откроется чат этой работы.'}</div>}</div>
        </div>
      </Modal>);
  }

  /* ───── данные и маршрутизация окон ───── */
  function collect(me) {
    const t = t0();
    return { projs: V.projects(me.id), orders: V.orders(me.id), chats: V.chats(me.id), act: V.activity(me.id, t) };
  }

  function App() {
    useStore();
    const me = useMe();
    const [modal, open, close] = useHash();
    // замерщик — прежние «Выезды на замер»
    React.useEffect(() => { if (me.id && me.trade === 'measurer') window.location.replace(keep('executor-tasks.html')); }, [me.id, me.trade]);
    if (!P || !V) return <div className="cv-page"><div className="empty">Кабинет не загрузился. Обновите страницу.</div></div>;
    const deny = denied(me);
    if (deny) return (
      <div className="cv-page lw-page"><main id="main" className="cv-main"><section className="cv-hero"><div><h1 className="cv-hello">Нет доступа к кабинету</h1><div className="lab">{deny} Обратитесь к прорабу или руководителю.</div></div></section></main></div>);
    if (!me.id || me.trade === 'measurer') return (
      <div className="cv-page lw-page"><Header nav={NAV} cur="" right={null} />
        <main id="main" className="cv-main"><section className="cv-hero"><div><h1 className="cv-hello">Кабинет мастера</h1><div className="lab">Откройте кабинет по личной ссылке — её присылает прораб или руководитель.</div></div></section></main></div>);
    const data = collect(me);
    const arg = (k) => (modal.indexOf(k + '=') === 0 ? modal.slice(k.length + 1) : null);
    return (
      <React.Fragment>
        <Home me={me} data={data} open={open} />
        {(arg('proj') || modal === 'money') && !me.money && <Modal title="Раздел закрыт" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Смета и деньги не входят в выданный вам доступ. Обратитесь к руководителю.</div></Modal>}
        {(modal === 'chat' || arg('chat') != null) && !me.chat && <Modal title="Раздел закрыт" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Чаты не входят в выданный вам доступ. Обратитесь к руководителю.</div></Modal>}
        {arg('proj') && me.money && <ProjectModal key={arg('proj')} id={arg('proj')} me={me} open={open} close={close} />}
        {modal === 'money' && me.money && <MoneyModal me={me} data={data} open={open} close={close} />}
        {(modal === 'chat' || arg('chat') != null) && me.chat && <ChatModal me={me} data={data} sel={arg('chat')} open={open} close={close} />}
      </React.Fragment>);
  }

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