/* designer-v2-app.jsx — кабинет дизайнера в виде кабинета клиента (KSH-453): шапка, главная со сводкой, разделы — окнами по #hash.
   Собран из библиотеки виджетов lk-widgets.jsx (window.LKW) на данных designer-views.js (window.DesignerViews).
   Главная: сводка по дизайн-проектам, «Сейчас важно» (что ждёт дизайнера), «Мои ДП» (на ком ход), материалы на решении, чаты,
   объекты на стройке, «Мои задачи» (общий таск-менеджер: задачи сделок и решения).
   Окна: #deals[=<корзина>] — все ДП по корзинам прежних «Мои ДП»; #deal=<id> — сделка: этапы ДП (файлы, замечания клиента,
   «Отправить клиенту» — DpPipeline.send, «Готово» для чертежей и надзора — DpPipeline.markDone), бриф (DpBriefModel), спецификация,
   команда и чаты, «Взять в работу» для «Ждут старта» (DealTeam.assign); #objects, #obj=<pid> — укомплектованность и чистовые материалы
   (ProjectStore.decideMaterial); #dec=<kind>:<pid> — решение из «Мои задачи»; #chat[=<src>:<id>:<tid>].
   Загрузка файлов этапа, заметки, таблица спецификации и анкета брифа (согласование брифа) — в прежнем рабочем месте ДП
   (designer-design.html?deal=), туда ведут кнопки окна сделки. Авторский надзор, лента и профиль — в «Ещё». */
(function () {
  'use strict';
  const DV = window.DesignerViews, 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, ['designer', 'super', 'executor']);
    // исполнитель с типом «дизайнер» — тоже дизайнер (как в прежнем кабинете); прочие исполнители — чужая роль
    const other = acc && acc.role === 'executor' && acc.trade !== 'designer';
    const home = ac.zone('design'), objs = ac.zone('completeness'), chat = ac.zone('chat');
    return { acc, name: (acc && acc.name) || 'Дизайнер', all: !acc || acc.role === 'super',
      deny: other ? 'Эта ссылка открывает кабинет другой роли.' : ac.deny || (home || objs || chat ? '' : 'Кабинет дизайнера не входит в выданный доступ.'), home, objs, chat };
  }
  const EVS = ['storage', 'obitel:sync', 'obitel:dp-pipeline', 'obitel:deal-chat', 'obitel:deal-docs', 'remontpro:forms-updated', 'remontpro:intake-updated', '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 go = (url) => { window.location.href = url; };
  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 work = (id) => keep('designer-design.html', { deal: id });
  const album = (id, tab) => 'dp-album.html?deal=' + encodeURIComponent(id) + '&tab=' + tab;

  const NAV = [['', 'Главная'], ['deals', 'Мои ДП'], ['objects', 'Объекты'], ['chat', 'Чаты']];
  const MORE = [['designer-design.html', 'Рабочее место ДП'], ['designer-notes.html', 'Авторский надзор'], ['designer-activity.html', 'Лента'], ['tools.html?role=designer', 'Инструменты'], ['designer-profile.html', 'Мой профиль']];
  const IC = { brief: 'doc', contract: 'pause', work: 'tool', review: 'clock', changes: 'alert', done: 'check' };

  /* «Мои задачи»: задачи сделок (бриф, этапы и правки ДП — ставят сами процессы) и решения по объектам (TaskViews.decisionRows) */
  function myInbox(open) {
    const T = window.TaskViews; if (!T || !T.inbox) return [];
    const order = (r) => (r.late ? 0 : r.now ? 1 : 2);
    const dec = (T.decisionRows ? T.decisionRows('designer') : []).map((r) => Object.assign({}, r, { act: () => (r.dkind === 'gate' ? go(keep('designer-design.html')) : open('dec=' + r.dkind + ':' + (r.pid || ''))) }));
    const deal = T.inbox('designer', 'designer', t0()).map((r) => Object.assign({}, r, { act: () => {
      if (r.kind === 'returned' && r.dealId && r.key && T.markTaskSeen) T.markTaskSeen(r.dealId, r.key, { role: 'designer', name: 'Дизайнер' });
      if (r.dealId && (/^(dp_|dc_dp)/.test(r.key || '') || /designer-design\.html/.test(r.href || ''))) open('deal=' + r.dealId); else if (r.href) go(r.href);
    } }));
    return dec.concat(deal).sort((a, b) => order(a) - order(b) || String(a.due || '9999').localeCompare(String(b.due || '9999')));
  }

  /* ───── главная ───── */
  function Home({ me, data, open }) {
    const { rows, f, objs, chats } = data;
    const T = window.TaskViews;
    const first = (me.name || '').split(' ')[0];
    const mineRows = rows.filter((r) => r.who === 'designer');
    const pend = objs.reduce((a, o) => a + o.pending.length, 0);
    const now = mineRows.slice(0, 3).map((r) => ({ key: r.dealId, kindL: r.bucket === 'brief' ? 'Бриф' : r.bucket === 'changes' ? 'Правки клиента' : 'Дизайн-проект', late: r.bucket === 'changes',
      title: r.client, sub: r.status, act: { text: r.briefState === 'submitted' ? 'Открыть бриф' : 'Открыть', main: true, onClick: () => open('deal=' + r.dealId) } }));
    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()} · {f.total} {plural(f.total, 'дизайн-проект', 'дизайн-проекта', 'дизайн-проектов')}</div><h1 className="cv-hello">{greeting()}{first ? ', ' + first : ''}</h1></div>
            <div className="cv-hero-act">
              {f.mine ? <button type="button" className="btn-k" onClick={() => open('deals=mine')}>Ждут вас · {f.mine}</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, 2).join(', ')}</span></React.Fragment> : <span>Новых сообщений нет</span>}</span>}
            facts={[
              { v: f.brief + ' ' + plural(f.brief, 'бриф', 'брифа', 'брифов'), lab: f.briefSubmitted ? f.briefSubmitted + ' ' + plural(f.briefSubmitted, 'ждёт', 'ждут', 'ждут') + ' согласования' : 'Анкеты клиентов', onClick: () => open('deals=brief'), late: f.briefSubmitted > 0 },
              { v: f.work + ' в работе', lab: 'Этапы у вас', onClick: () => open('deals=work') },
              { v: f.changes + ' ' + plural(f.changes, 'правка', 'правки', 'правок'), lab: 'Клиент просит изменить', onClick: () => open('deals=changes'), late: f.changes > 0 },
              { v: f.review + ' у клиента', lab: 'Ждут согласования', onClick: () => open('deals=review') },
              { v: f.contract + ' ждут старта', lab: 'Договор, аванс или назначение', onClick: () => open('deals=contract') },
            ]} />

          <NowCards rows={now} />

          <section className="cv-two lw-two">
            <RowsW id="dz-deals-h" title="Мои дизайн-проекты" n={f.mine} className="is-grow" text={'Все' + (rows.length > 8 ? ' · ' + rows.length : '')} onOpen={() => open('deals')}
              rows={rows.slice(0, 8).map((r) => ({ key: r.dealId, ic: IC[r.bucket] || 'doc', late: r.bucket === 'changes', title: r.client + ' · ' + r.dealId, sub: r.status, right: r.whoL, onClick: () => open('deal=' + r.dealId) }))}
              empty="Сделок с дизайн-проектом пока нет — они появляются, когда менеджер отмечает «Дизайн-проект: да»." />
            <div className="cv-side">
              {me.objs && <RowsW id="dz-mat-h" title="Материалы на решении" n={pend} max={300} text="Объекты" onOpen={() => open('objects')}
                rows={objs.filter((o) => o.pending.length).map((o) => ({ key: o.id, ic: 'doc', title: o.title, sub: o.pending.length + ' ' + plural(o.pending.length, 'чистовой материал', 'чистовых материала', 'чистовых материалов') + ' ждут вас', right: o.compl + ' %', rightLab: 'согласовано', onClick: () => open('obj=' + o.id) }))}
                empty="Все чистовые материалы согласованы." />}
              {me.chat && <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>

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

          {T && T.inbox && <section id="dz-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(open), empty: 'Сейчас на вас ничего нет.', boardRows: myInbox(open).concat(T.inboxDone ? T.inboxDone('designer', t0()) : []), columns: T.IN_COLUMNS, colOf: T.inCol },
              ].concat(T.outbox ? [{ id: 'out', label: 'Отправлено мной', rows: T.outbox({ role: 'designer' }, t0()), empty: 'Вы ничего не возвращали на доработку.',
                buckets: T.OUT_BUCKETS, bucketOf: T.outBucket, boardRows: T.outbox({ role: 'designer' }, 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.pending.length ? <span className="fl"><span>{o.pending.length} ждут решения</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 dz-bar"><i style={{ width: o.compl + '%' }} /></span>
          <span className="row"><span className="st">{o.stage}</span><span className="pc">{o.compl} %</span></span>
          <span className="lab">чистовых материалов согласовано</span>
        </span>
      </button>);
  }

  /* ───── все ДП: корзины прежних «Мои ДП» ───── */
  function DealsModal({ data, sel, open, close }) {
    const [tab, setTab] = React.useState(sel === 'mine' || DV.BUCKETS.some(([id]) => id === sel) ? sel : 'all');
    const kinds = [['mine', 'Ждут вас']].concat(DV.BUCKETS);
    const of = (k) => (k === 'mine' ? data.rows.filter((r) => r.who === 'designer') : k === 'all' ? data.rows : data.rows.filter((r) => r.bucket === k));
    const list = of(tab);
    return (
      <Modal size="wide" title="Дизайн-проекты" sub={data.f.total + ' всего · ждут вас ' + data.f.mine} onClose={close}>
        <div className="mg-tabs"><W.CalTabs kinds={kinds} tab={tab} setTab={setTab} counts={kinds.reduce((a, [k]) => Object.assign(a, { [k]: of(k).length }), {})} label="Какие сделки показать" /></div>
        {list.length ? <div className="mg-list">{list.map((r) => (
          <button type="button" key={r.dealId} className="mg-deal" onClick={() => open('deal=' + r.dealId)}>
            <span className="t">{r.client} · {r.dealId}</span><span className="lab">{r.address} · {r.status}</span>
            <span className="r"><b className={r.bucket === 'changes' ? 'mg-late' : ''}>{r.whoL}</b><span className="lab">{r.step >= 1 ? r.done + ' из ' + r.total + ' этапов' : 'бриф ' + r.briefPct + ' %'}</span></span>
          </button>))}</div> : <div className="note-s" style={{ marginTop: 18 }}><span>В этом фильтре сделок нет.</span></div>}
      </Modal>);
  }

  /* ───── сделка: этапы ДП, бриф, спецификация, команда ───── */
  const ST_CLS = { approved: 'pill-g', done: 'pill-g', review: 'pill-n', changes: 'pill-n cv-over', draft: 'pill-d', pending: 'pill-d' };
  function DealModal({ id, me, data, open, close }) {
    const d = DV.deal(id, me.acc);
    const [tab, setTab] = React.useState('stages');
    const [err, setErr] = React.useState('');
    if (!d) return <Modal title="Сделка не найдена" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такой сделки нет или у неё нет дизайн-проекта.</div></Modal>;
    const D = window.DpPipeline;
    const run = (fn) => { try { fn(); setErr(''); } catch (e) { setErr(e.message); } };
    const claim = () => run(() => window.DealTeam.assign(id, 'designer', { id: me.acc.profileId, name: me.acc.name, how: 'взял в работу' }));
    const chats = data.chats.filter((c) => c.dealId === id);
    const tabs = [['stages', 'Этапы'], ['brief', 'Бриф'], ['spec', 'Спецификация'], ['team', 'Команда и чат']];
    return (
      <Modal size="wide" title={d.client} sub={[id, d.address, d.area ? d.area + ' м²' : ''].filter(Boolean).join(' · ')} onClose={close}
        head={<span className="dz-head"><span className={d.who === 'designer' ? 'pill-n cv-over' : 'pill-d'}>{d.whoL}</span><a className="cv-arrow-btn" href={work(id)}>Рабочее место ДП<Ic n="ne" s={14} /></a></span>}>
        <ol className="mg-steps" aria-label="Путь дизайн-проекта">
          <li className={d.step > 0 ? 'is-done' : 'is-cur'}>Бриф</li>
          {d.stages.map((s) => <li key={s.id} className={s.status === 'approved' || s.status === 'done' ? 'is-done' : d.step >= 1 && d.bucket !== 'contract' && (s.status === 'draft' || s.status === 'review' || s.status === 'changes') ? 'is-cur' : ''}>{s.short}</li>)}
        </ol>
        <div className="dz-status lab">{d.status}</div>
        {err && <div className="att" role="alert" style={{ marginTop: 12 }}><Ic n="alert" s={16} /><span>{err}</span></div>}
        <div className="mg-tabs"><W.CalTabs kinds={tabs} tab={tab} setTab={setTab} label="Раздел сделки" /></div>

        {tab === 'stages' && <div className="dz-stages">
          {d.bucket === 'brief' && <div className="card dz-card">
            <div className="t">Сначала бриф</div>
            <div className="lab" style={{ marginTop: 6 }}>Анкета клиента: {d.status.replace(/^Бриф · /, '')}. Этапы ДП откроются после согласования брифа, договора на ДП и аванса.</div>
            <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: 14 }}><a className="btn-k" href={work(id)}>{d.briefState === 'submitted' ? 'Открыть и согласовать бриф' : 'Заполнить бриф'}</a></div>
          </div>}
          {d.bucket === 'contract' && d.gate && <div className="card dz-card">
            <div className="t">Этапы начнутся после договора, аванса и закрепления дизайнера</div>
            <div className="lab" style={{ marginTop: 6 }}>Не хватает: {d.gate.missing.join(', ')}.</div>
            {d.canClaim && d.gate.people && d.gate.people.length > 0 && <div className="cv-dbtn" style={{ display: 'flex', marginTop: 14 }}><button type="button" className="btn-k" onClick={claim}>Взять в работу</button></div>}
          </div>}
          {d.step >= 1 && d.bucket !== 'contract' && d.stages.map((s) => (
            <div key={s.id} className={'card dz-stage' + (s.status === 'changes' ? ' is-over' : '')}>
              <div className="cv-chead"><div style={{ minWidth: 0 }}><div className="t">{s.n}. {s.title}{s.version > 1 ? <span className="lab"> · версия {s.version}</span> : null}</div><div className="lab" style={{ marginTop: 3 }}>{s.result}</div></div>
                <span className={ST_CLS[s.status] || 'pill-d'}>{s.statusL}</span></div>
              {s.note ? <div className="dz-note">{s.note}</div> : null}
              {s.items.length > 0 && <div className="dz-files">{s.items.slice(0, 6).map((it) => it.kind === 'image'
                ? <a key={it.id} href={it.url} target="_blank" rel="noreferrer" className="dz-th"><img src={it.url} alt={it.name || ''} loading="lazy" /></a>
                : <a key={it.id} href={it.url} target="_blank" rel="noreferrer" className="pill-n">{it.name || 'файл'}</a>)}{s.items.length > 6 ? <span className="lab">ещё {s.items.length - 6}</span> : null}</div>}
              {s.id === 'spec' && s.rows > 0 ? <div className="lab" style={{ marginTop: 8 }}>{s.rows} {plural(s.rows, 'позиция', 'позиции', 'позиций')} в спецификации</div> : null}
              {s.status === 'changes' && s.lastChange ? <div className="att" style={{ marginTop: 10 }}><Ic n="alert" s={16} /><span>Клиент: «{s.lastChange}»</span></div> : null}
              {d.bucket !== 'contract' && (s.status !== 'pending' || s.canSend) && <div className="cv-dbtn dz-acts">
                {s.status !== 'approved' && s.status !== 'done' && s.status !== 'review' && <a className="btn-l" href={work(id)}>{s.items.length || s.note ? 'Дополнить' : 'Загрузить материалы'}</a>}
                {s.canSend && <button type="button" className="btn-k" onClick={() => run(() => D.send(id, s.id))}>{s.status === 'changes' ? 'Отправить новую версию' : 'Отправить клиенту'}</button>}
                {s.canDone && <button type="button" className="btn-k" onClick={() => run(() => D.markDone(id, s.id))}>Готово</button>}
                {s.status === 'review' && <span className="lab">Отправлено клиенту{s.sentAt ? ' ' + dateR(String(s.sentAt).slice(0, 10), { month: 'short' }) : ''} · ждём решения</span>}
                {(s.status === 'draft' || s.status === 'changes') && !s.ready && <span className="lab">Добавьте файлы или заметку — тогда этап можно отправить</span>}
              </div>}
            </div>))}
          {d.step >= 2 && <div className="dz-acts"><a className="btn-l" href={album(id, 'pres')} target="_blank" rel="noreferrer">Презентация</a><a className="btn-l" href={album(id, 'tech')} target="_blank" rel="noreferrer">Техчасть</a></div>}
        </div>}

        {tab === 'brief' && <BriefPane id={id} d={d} />}

        {tab === 'spec' && (d.spec.rows.length ? <div className="dz-spec">
          <div className="lab" style={{ marginTop: 16 }}>{d.spec.rows.length} {plural(d.spec.rows.length, 'позиция', 'позиции', 'позиций')} · {rub(d.spec.total)} · покупаем мы: {d.spec.ours}, клиент сам: {d.spec.rows.length - d.spec.ours}</div>
          <div className="mg-rows" style={{ marginTop: 8 }}>{d.spec.rows.map((r) => (
            <div key={r.id} className="r"><span className="tx"><span className="t">{r.name || 'Без названия'}</span><span className="lab">{[r.room, r.article, r.qty + ' ' + (r.unit || ''), r.buyer === 'client' ? 'покупает клиент' : 'покупаем мы'].filter(Boolean).join(' · ')}</span></span>
              <span className="by"><b>{rub((+r.qty || 0) * (+r.price || 0))}</b></span></div>))}</div>
          <div className="dz-acts"><a className="btn-l" href={work(id)}>Редактировать в рабочем месте</a></div>
        </div> : <div className="note-s" style={{ marginTop: 18 }}><span>Спецификации пока нет — она собирается на пятом этапе.</span></div>)}

        {tab === 'team' && <div className="cv-two lw-two dz-team">
          <section className="cv-w" aria-labelledby="dz-team-h"><W.WHead id="dz-team-h" title="Команда сделки" />
            <div className="mg-rows" style={{ marginTop: 8 }}>{d.team.length ? d.team.map((p) => (
              <div key={p.role} className="r"><span className="tx"><span className="t">{p.name}</span><span className="lab">{(DV.RU[p.role] === 'Вы' ? 'Дизайнер' : DV.RU[p.role] || p.role) + (p.src ? ' · ' + p.src : '')}</span></span>
                {p.phone ? <a className="lw-btn-l" href={'tel:' + String(p.phone).replace(/[^\d+]/g, '')}>Позвонить</a> : null}</div>)) : <div className="lab">Команда ещё не назначена.</div>}</div>
          </section>
          <section className="cv-w" aria-labelledby="dz-dchat-h"><W.WHead id="dz-dchat-h" title="Чаты сделки" onOpen={me.chat ? () => open('chat') : null} label="Все чаты" />
            <div className="mg-rows" style={{ marginTop: 8 }}>{chats.length && me.chat ? chats.map((c) => (
              <button type="button" key={c.id} className="r mg-row" onClick={() => open('chat=' + c.src + ':' + c.pid + ':' + c.id)}><span className="tx"><span className="t">{c.title}</span><span className="lab">{c.who ? c.who + ': ' : ''}{c.text}</span></span><span className="by lab">{when(c.at)}</span></button>))
              : <div className="lab">{me.chat ? 'Переписки по сделке пока нет.' : 'Чаты не входят в выданный доступ.'}</div>}</div>
          </section>
        </div>}
      </Modal>);
  }

  // бриф клиента — той же раскладкой, что у клиента на странице «Дизайн-проект» (DpBriefModel)
  function BriefPane({ id, d }) {
    const M = window.DpBriefModel; const b = M && M.forDeal ? M.forDeal(id) : null;
    if (!b || !b.ready) return (
      <div className="card dz-card" style={{ marginTop: 18 }}><div className="t">Бриф {b ? b.statusL || '' : ''}</div>
        <div className="lab" style={{ marginTop: 6 }}>Ответы появятся здесь, когда клиент отправит анкету{b && b.progress ? ' · заполнено ' + b.progress + ' %' : ''}.</div>
        <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: 14 }}><a className="btn-k" href={work(id)}>Открыть анкету</a></div></div>);
    return (
      <div className="dz-brief">
        <div className="dz-brief-h"><span className={b.status === 'approved' ? 'pill-g' : 'pill-n'}>{b.statusL}</span>{b.lede ? <span className="lab">{b.lede}</span> : null}
          {b.status !== 'approved' && <a className="btn-k" href={work(id)}>Открыть и согласовать</a>}</div>
        {(b.facts || []).length > 0 && <dl className="dz-facts">{b.facts.slice(0, 8).map((x) => <div key={x.label}><dt>{x.label}</dt><dd>{Array.isArray(x.value) ? x.value.join(', ') : x.value}</dd></div>)}</dl>}
        {(b.groups || []).map((g) => (
          <section key={g.id} className="dz-bgroup"><h3 className="t">{g.title}</h3>
            {g.narrative ? <p className="lab">{g.narrative}</p> : null}
            {(g.blocks || []).map((bl, i) => <BBlock key={i} b={bl} />)}
          </section>))}
      </div>);
  }

  // блоки брифа по типам DpBriefModel.secBlocks: факты, текст, фото, стили, комнаты, референсы (как у клиента в client-v2-design.jsx)
  const val = (v) => (Array.isArray(v) ? v.join(', ') : String(v));
  const Pic = ({ url, cap }) => <a className="dz-th" href={url} target="_blank" rel="noreferrer" title={cap || ''}><img src={url} alt={cap || ''} loading="lazy" /></a>;
  function BBlock({ b }) {
    if (b.t === 'facts') return <React.Fragment>{b.rows.map((r) => <div key={r.label} className="dz-brow"><span className="lab">{r.label}</span><span>{val(r.value)}</span></div>)}</React.Fragment>;
    if (b.t === 'text') return <div className="dz-brow"><span className="lab">{b.label}</span><span>{b.text}</span></div>;
    if (b.t === 'photos') return <div className="dz-brow"><span className="lab">{b.label}</span><span className="dz-files">{b.urls.map((u) => <Pic key={u} url={u} />)}</span></div>;
    if (b.t === 'styles') return (
      <React.Fragment>
        {b.like.map((c) => <div key={c.id} className="dz-brow"><span className="lab">Нравится</span><span><b>{c.title}</b>{c.sub ? ' · ' + c.sub : ''}<span className="dz-files">{c.images.map((u) => <Pic key={u} url={u} cap={c.title} />)}</span></span></div>)}
        {b.unsure.length > 0 && <div className="dz-brow"><span className="lab">Сомневается</span><span>{b.unsure.join(', ')}</span></div>}
        {b.dislike.length > 0 && <div className="dz-brow"><span className="lab">Не нравится</span><span>{b.dislike.join(', ')}</span></div>}
      </React.Fragment>);
    if (b.t === 'rooms') return <React.Fragment>{b.rows.map((r) => <div key={r.name} className="dz-brow"><span className="lab">{r.name}{r.area ? ' · ' + r.area : ''}</span>
      <span>{[r.priorityL ? 'приоритет: ' + r.priorityL : '', r.mood.join(', '), r.note].filter(Boolean).join(' · ')}</span></div>)}</React.Fragment>;
    if (b.t === 'gallery') return (
      <React.Fragment>
        {b.images.length > 0 && <div className="dz-files">{b.images.map((x, i) => <Pic key={x.url + i} url={x.url} cap={x.title} />)}</div>}
        {b.links.length > 0 && <div className="dz-files">{b.links.map((x) => <a key={x.url} className="pill-n" href={x.url} target="_blank" rel="noreferrer">{x.title}</a>)}</div>}
      </React.Fragment>);
    return null;
  }

  /* ───── объекты на стройке: укомплектованность и чистовые материалы ───── */
  function ObjectsModal({ data, open, close }) {
    return (
      <Modal size="wide" title="Объекты на стройке" sub={data.objs.length + ' с дизайн-проектом'} onClose={close}>
        {data.objs.length ? <div className="lw-tiles" style={{ marginTop: 18 }}>{data.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>);
  }
  function ObjModal({ id, data, me, open, close }) {
    const o = data.objs.find((x) => x.id === id);
    if (!o) return <Modal title="Объект не найден" onClose={close}><div className="lab" style={{ marginTop: 16 }}>Такого объекта нет или у него нет дизайн-проекта.</div></Modal>;
    return (
      <Modal size="wide" title={o.title} sub={[o.client, o.id, o.stage].filter(Boolean).join(' · ')} onClose={close}
        head={o.dealId ? <button type="button" className="cv-arrow-btn" onClick={() => open('deal=' + o.dealId)}>Дизайн-проект<Ic n="ne" s={14} /></button> : null}>
        <MatDecisions pid={id} me={me} />
        <section className="dz-sec"><h3 className="t">Укомплектованность по этапам</h3>
          {o.rooms.length ? <div className="mg-why">{o.rooms.map((r) => { const pct = r.selected ? Math.round(r.client / r.selected * 100) : 0; return (
            <div key={r.room} className="mg-whyr"><span>{r.room}</span><span className="mg-bar"><i style={{ width: pct + '%' }} /></span><span className="lab">{r.client}/{r.selected} согласовано · {r.onsite} на объекте</span></div>); })}</div>
            : <div className="lab">Материалов в проекте пока нет.</div>}
        </section>
        <div className="dz-acts"><a className="btn-l" href={keep('designer-notes.html', { project: id })}>Авторский надзор</a><a className="btn-l" href={keep('designer-design.html', { zone: 'smm' })}>Работы для соцсетей{o.stars ? ' · ★ ' + o.stars : ''}</a></div>
      </Modal>);
  }
  // чистовые материалы на решении дизайнера: тот же ProjectStore.decideMaterial, что в прежних «Согласованиях»
  function MatDecisions({ pid, me }) {
    const [rej, setRej] = React.useState(null); const [why, setWhy] = React.useState(''); const [err, setErr] = React.useState('');
    const rows = (P && P.views.approvals ? P.views.approvals() : []).filter((a) => a.status === 'pending' && (!pid || a.pid === pid));
    const actor = { role: 'designer', name: me.name };
    const decide = (a, st, c) => { try { P.decideMaterial(a.pid, a.matId, st, actor, c); setErr(''); setRej(null); setWhy(''); } catch (e) { setErr(e.message); } };
    return (
      <section className="dz-sec"><h3 className="t">Чистовые материалы на вашем решении{rows.length ? ' · ' + rows.length : ''}</h3>
        {err && <div className="att" role="alert"><Ic n="alert" s={16} /><span>{err}</span></div>}
        {rows.length ? <div className="mg-rows">{rows.map((a) => (
          <div key={a.pid + a.matId} className="r dz-mat"><span className="tx"><span className="t">{a.t}</span><span className="lab">{a.kind} · {a.target}</span>
            {rej === a.matId && <textarea className="cv-ta" rows={2} autoFocus placeholder="Что поправить — увидят закупки и менеджер" aria-label="Что поправить" value={why} onChange={(e) => setWhy(e.target.value)} />}</span>
            <span className="lw-racts">{rej === a.matId
              ? <React.Fragment><button type="button" className="lw-btn-l" onClick={() => setRej(null)}>Отмена</button><button type="button" className="lw-btn-k" disabled={!why.trim()} onClick={() => decide(a, 'rejected', why)}>Отправить</button></React.Fragment>
              : <React.Fragment><button type="button" className="lw-btn-l" onClick={() => { setRej(a.matId); setWhy(''); }}>Правки</button><button type="button" className="lw-btn-k" onClick={() => decide(a, 'approved')}>Согласовать</button></React.Fragment>}</span>
          </div>))}</div> : <div className="note-s"><Ic n="check" c="#2F5D3A" s={16} /><span>Всё согласовано.</span></div>}
      </section>);
  }

  /* ───── решения из «Мои задачи»: чистовые материалы, замены, правки клиента по спецификации ───── */
  const DEC_T = { mat: 'Чистовые материалы', rpl: 'Замена материала', spec: 'Правки клиента по спецификации' };
  function DecModal({ arg, me, close }) {
    const [kind, pid] = arg.split(':');
    const [err, setErr] = React.useState(''); const [f, setF] = React.useState({}); const [why, setWhy] = React.useState({}); const [rej, setRej] = React.useState(null);
    const actor = { role: 'designer', name: me.name };
    const p = pid && P ? P.get(pid) : null;
    const run = (fn) => { try { fn(); setErr(''); setRej(null); } catch (e) { setErr(e.message); } };
    let body = null;
    if (kind === 'mat') body = <MatDecisions pid={pid} me={me} />;
    if (kind === 'rpl') { const rows = (P.views.pendingReplacements ? P.views.pendingReplacements('designer') : []).filter((r) => !pid || r.pid === pid);
      body = rows.length ? <div className="cv-cards" style={{ marginTop: 18 }}>{rows.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)}</dd></div><div><dt>Аналог</dt><dd>{r.prop.supplier} · {rub(r.prop.price)}</dd></div></dl>
          {rej === r.id && <textarea className="cv-ta" rows={2} autoFocus placeholder="Почему — увидят в истории" aria-label="Причина" value={why[r.id] || ''} onChange={(e) => setWhy(Object.assign({}, why, { [r.id]: e.target.value }))} />}
          <div className="cv-dbtn" style={{ display: 'flex', gap: 8, marginTop: 12 }}>{rej === r.id
            ? <React.Fragment><button type="button" className="btn-l" onClick={() => setRej(null)}>Отмена</button><button type="button" className="btn-k" disabled={!(why[r.id] || '').trim()} onClick={() => run(() => P.decideReplacement(r.pid, r.id, 'reject', why[r.id].trim(), actor))}>Отправить</button></React.Fragment>
            : <React.Fragment><button type="button" className="btn-l" onClick={() => setRej(r.id)}>Отклонить</button><button type="button" className="btn-k" onClick={() => run(() => P.decideReplacement(r.pid, r.id, 'approve', '', actor))}>Согласовать</button></React.Fragment>}</div>
        </div>))}</div> : null; }
    if (kind === 'spec') { const rows = (P.views.specRevisions ? P.views.specRevisions('designer') : []).filter((r) => !pid || r.pid === pid);
      body = rows.length ? <div className="cv-cards" style={{ marginTop: 18 }}>{rows.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>); })}</div> : null; }
    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>}
        {body || <div className="note-s" style={{ marginTop: 18 }}><Ic n="check" c="#2F5D3A" s={16} /><span>Решено — здесь больше ничего не ждёт вас.</span></div>}
      </Modal>);
  }

  /* ───── чаты: сделки и проекты, где есть дизайнер ───── */
  // вложение: видео — с плеером (как в прежнем чате lk-features.jsx), картинка — картинкой, остальное — ссылкой
  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({ 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, 'designer'); }, [cur && cur.pid, cur && cur.id]);
    const send = () => {
      if (!txt.trim() || !thread) return;
      if (cur.src === 'project') P.chatSend(cur.pid, thread.id, { from: 'designer', t: txt.trim() }); else DC.send(cur.pid, thread.id, { from: 'designer', 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 !== 'designer').map((m) => DV.RU[m] || m).join(', ')}</span>
              {cur.dealId ? <button type="button" className="cv-arrow-btn" onClick={() => open('deal=' + cur.dealId)}>Дизайн-проект<Ic n="ne" s={14} /></button> : null}</div>
            <div className="lw-msgs cv-scroll">{thread.msgs.map((m) => (
              <div key={m.id} className={'lw-msg' + (m.from === 'designer' ? ' mine' : '')}><span className="lab">{DV.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 collect(me) {
    const all = DV.deals(me.acc);
    // у дизайнера — свои сделки и ещё ни за кем не закреплённые; руководитель и демо без аккаунта видят все
    const rows = me.all ? all : all.filter((r) => r.mine);
    return { rows, f: DV.facts(rows), objs: DV.objects(), chats: DV.chats() };
  }
  function App() {
    useStore();
    const me = useMe();
    const [modal, open, close] = useHash();
    if (!DV) return <div className="cv-page"><div className="empty">Кабинет не загрузился. Обновите страницу.</div></div>;
    if (me.deny) return <W.Denied why={me.deny} />;
    const data = collect(me);
    const arg = (k) => (modal.indexOf(k + '=') === 0 ? modal.slice(k.length + 1) : null);
    const dealWin = modal === 'deals' || !!arg('deals') || !!arg('deal') || !!arg('dec');
    const objWin = modal === 'objects' || !!arg('obj');
    const chatWin = modal === 'chat' || arg('chat') != null;
    return (
      <React.Fragment>
        {me.home ? <Home me={me} data={data} open={open} /> : <W.Denied title="Главная закрыта" why="Главная кабинета не входит в выданный доступ — открытые вам разделы работают по своим ссылкам." />}
        {dealWin && !me.home && <W.Closed what="Раздел «Мои ДП»" onClose={close} />}
        {objWin && !me.objs && <W.Closed what="Раздел «Комплектация»" onClose={close} />}
        {chatWin && !me.chat && <W.Closed what="Раздел «Чаты»" onClose={close} />}
        {(modal === 'deals' || arg('deals')) && me.home && <DealsModal key={modal} data={data} sel={arg('deals')} open={open} close={close} />}
        {arg('deal') && me.home && <DealModal key={arg('deal')} id={arg('deal')} me={me} data={data} open={open} close={close} />}
        {arg('dec') && me.home && <DecModal key={arg('dec')} arg={arg('dec')} me={me} close={close} />}
        {modal === 'objects' && me.objs && <ObjectsModal data={data} open={open} close={close} />}
        {arg('obj') && me.objs && <ObjModal key={arg('obj')} id={arg('obj')} data={data} me={me} open={open} close={close} />}
        {chatWin && me.chat && <ChatModal data={data} sel={arg('chat')} open={open} close={close} me={me} />}
      </React.Fragment>);
  }

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