Files
portal/web/v8/deals-variants.html
T
Дмитрий 55a34af986 feat(deals): redesign groundwork — spec, plan, mockups + sidebar nav cleanup
Deals page redesign: design spec + implementation plan (Phase A page redesign,
Phase B 14->5 status funnel) + v8 HTML mockups (variants comparison + final).
AppSidebar: remove Импорт данных / Отчёты nav links (routes stay reachable by
direct URL); AppLayout.spec updated to 6 nav items. stylelint --fix on mockups;
cspell-words += deals-redesign terms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 03:42:39 +03:00

507 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Сделки — 4 варианта дизайна · Лидерра</title>
<style>
:root{
--teal:#0F6E56; --ivory:#F6F3EC; --noir:#012019; --surface:#ffffff;
--line:#E6E2D6; --line-strong:#d4cfbe; --muted:#6b6356; --ink:#081319;
--radius:10px;
}
*{box-sizing:border-box;}
body{
margin:0; background:var(--ivory); color:var(--ink);
font-family:'Inter','Segoe UI',system-ui,sans-serif; font-size:14px; line-height:1.5;
}
.mono{font-family:'JetBrains Mono',ui-monospace,monospace;font-feature-settings:'tnum';}
h1{font-size:24px;margin:0 0 4px;letter-spacing:-.02em;}
h2{font-size:19px;margin:0 0 2px;letter-spacing:-.015em;}
.wrap{max-width:1320px;margin:0 auto;padding:24px 28px 64px;}
/* ---- intro + tabs ---- */
.intro{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:18px 22px;margin-bottom:18px;}
.intro p{margin:6px 0;color:var(--muted);}
.tabs{display:flex;gap:8px;flex-wrap:wrap;margin:16px 0 8px;}
.tab{
padding:9px 18px;border:1px solid var(--line-strong);background:var(--surface);
border-radius:var(--radius);cursor:pointer;font:inherit;font-weight:600;color:var(--muted);
transition:all .15s;
}
.tab:hover{border-color:var(--teal);}
.tab.active{background:var(--teal);color:#ffffff;border-color:var(--teal);}
.variant{display:none;}
.variant.active{display:block;}
.caption{
background:#fffdf6;border:1px solid var(--line);border-left:3px solid var(--teal);
border-radius:6px;padding:12px 16px;margin:14px 0 18px;color:var(--muted);font-size:13px;
}
.caption b{color:var(--ink);}
/* ---- mock page shell ---- */
.page{background:var(--ivory);border:1px solid var(--line);border-radius:var(--radius);padding:22px;}
.pagehead{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:14px;}
.stats{color:var(--muted);font-size:13px;display:flex;gap:8px;flex-wrap:wrap;}
.stats .num{color:var(--teal);font-weight:600;}
.stats .sep{opacity:.4;}
.btn{
padding:7px 14px;border-radius:8px;border:1px solid var(--line-strong);background:var(--surface);
cursor:pointer;font:inherit;font-size:13px;color:var(--ink);
}
.btn.primary{background:var(--teal);color:#ffffff;border-color:var(--teal);}
.btn.ghost{background:transparent;border-color:transparent;color:var(--teal);}
/* ---- filters ---- */
.filterbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:12px;}
.fld{
background:var(--surface);border:1px solid var(--line);border-radius:8px;
padding:7px 11px;font-size:13px;color:var(--muted);display:inline-flex;align-items:center;gap:6px;
}
.fld.search{min-width:240px;flex:1;}
.fld .caret{opacity:.5;font-size:10px;}
.taskchips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;}
.taskchip{
background:var(--surface);border:1px solid var(--line);border-radius:20px;padding:5px 12px;
font-size:12px;color:var(--muted);cursor:pointer;
}
.taskchip b{color:var(--teal);font-family:'JetBrains Mono',monospace;}
.taskchip.on{border-color:var(--teal);background:rgba(15,110,86,.06);color:var(--teal);}
/* ---- table ---- */
.tbl-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;}
table{width:100%;border-collapse:collapse;}
th{
text-align:left;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;
color:var(--muted);padding:11px 12px;border-bottom:1px solid var(--line);background:#fbfaf6;
}
td{padding:11px 12px;border-bottom:1px solid var(--line);vertical-align:middle;}
tr:last-child td{border-bottom:none;}
tr.row:hover{background:#fbfaf6;}
.lead{display:flex;align-items:center;gap:10px;}
.ava{
width:32px;height:32px;border-radius:50%;background:var(--teal);color:#ffffff;
display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;flex-shrink:0;
}
.ava.sm{width:22px;height:22px;font-size:9px;background:var(--noir);}
.lead .nm{font-weight:500;}
.lead .ph{font-size:12px;color:var(--muted);}
.star{color:var(--line-strong);cursor:pointer;font-size:15px;}
.star.on{color:#d6a531;}
.dots{color:var(--muted);cursor:pointer;letter-spacing:1px;}
/* status pill */
.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:14px;font-size:12px;font-weight:500;white-space:nowrap;}
.pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.85;}
/* source badge */
.src{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border:1px solid var(--line);border-radius:7px;font-size:12px;background:var(--surface);}
.src .dot{width:7px;height:7px;border-radius:50%;}
.src .b{font-weight:600;}
.src .ty{color:var(--muted);}
.src.big{padding:6px 12px;font-size:13px;}
/* inline cells */
.inline{font-size:12px;color:var(--muted);}
.inline.set{color:var(--ink);}
.inline-edit{border:1px dashed var(--line-strong);border-radius:6px;padding:3px 8px;font-size:12px;color:var(--muted);display:inline-block;cursor:text;}
.rem{display:inline-flex;align-items:center;gap:5px;font-size:12px;}
.rem.due{color:#9a6b16;}
.price{font-family:'JetBrains Mono',monospace;font-weight:600;}
/* bulk bar */
.bulkbar{display:flex;align-items:center;gap:10px;background:var(--noir);color:#ffffff;border-radius:var(--radius);padding:10px 16px;margin-bottom:12px;font-size:13px;}
.bulkbar .btn{background:rgba(255,255,255,.1);color:#ffffff;border-color:rgba(255,255,255,.2);}
.bulkbar .sp{flex:1;}
/* ---- flow / inbox cards ---- */
.subtoggle{display:inline-flex;border:1px solid var(--line);border-radius:8px;background:var(--surface);padding:2px;margin-bottom:14px;}
.subtoggle button{border:none;background:transparent;padding:6px 16px;border-radius:6px;cursor:pointer;font:inherit;font-size:13px;color:var(--muted);}
.subtoggle button.on{background:rgba(1,32,25,.08);color:var(--noir);font-weight:600;}
.subview{display:none;}
.subview.on{display:block;}
.feed{display:flex;flex-direction:column;gap:10px;}
.lcard{
background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
padding:14px 16px;display:flex;align-items:center;gap:16px;
}
.lcard:hover{border-color:var(--line-strong);}
.lcard .when{font-size:11px;color:var(--muted);width:84px;flex-shrink:0;}
.lcard .main{flex:1;min-width:0;}
.lcard .main .nm{font-weight:600;}
.lcard .main .ph{font-size:12px;color:var(--muted);}
.lcard .meta{display:flex;align-items:center;gap:14px;flex-shrink:0;}
.lcard .resp{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);}
/* kanban */
.kanban{display:flex;gap:12px;overflow-x:auto;padding-bottom:8px;}
.kcol{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);min-width:212px;flex:1;}
.kcol h4{margin:0;padding:11px 13px;font-size:12px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;}
.kcol h4 span{color:var(--muted);font-family:'JetBrains Mono',monospace;}
.kcol .body{padding:10px;display:flex;flex-direction:column;gap:8px;min-height:60px;}
.kcard{border:1px solid var(--line);border-radius:8px;padding:9px 10px;}
.kcard .nm{font-weight:500;font-size:13px;}
.kcard .ph{font-size:11px;color:var(--muted);}
.kcard .ft{display:flex;justify-content:space-between;align-items:center;margin-top:6px;}
.legend{font-size:11px;color:var(--muted);margin-top:8px;}
</style>
</head>
<body>
<div class="wrap">
<div class="intro">
<h1>Страница «Сделки» — 4 варианта</h1>
<p>Лиды в портал поступают <b>только</b> от поставщика crm.bp по заказанным в проектах источникам (B1/B2/B3 × тип сигнала). Сделку нельзя создать вручную и нельзя удалить — у неё есть только статус. Во всех вариантах убраны кнопка «Новая сделка» и «Корзина».</p>
<p style="margin-top:10px;"><b>A</b> — минимальная коррекция текущей таблицы &nbsp;·&nbsp; <b>B</b> — паритет с «Мои сделки» поставщика &nbsp;·&nbsp; <b>C</b> — Лидерра-нативный инбокс (Поток/Воронка) &nbsp;·&nbsp; <b>Гибрид</b> — паритет-таблица + фишки C.</p>
<div class="tabs">
<button class="tab active" data-tab="a">Вариант A</button>
<button class="tab" data-tab="b">Вариант B</button>
<button class="tab" data-tab="c">Вариант C</button>
<button class="tab" data-tab="h">Гибрид</button>
</div>
</div>
<!-- ============ VARIANT A ============ -->
<section class="variant active" id="v-a">
<div class="caption"><b>Вариант A — Минимальная коррекция.</b> Текущая таблица сохранена, исправлены только ошибки модели: убрано ручное создание и корзина, колонки приведены к смыслу «лид от поставщика», добавлена колонка «Источник». Самый быстрый путь, но страница остаётся обычной таблицей без inline-работы с напоминанием/комментарием.</div>
<div class="page">
<div class="pagehead">
<div>
<h2>Сделки</h2>
<div class="stats"><span><span class="num">+3</span> новых с утра</span><span class="sep">·</span><span><span class="num">128</span> всего</span><span class="sep">·</span><span><span class="num">41</span> в работе</span></div>
</div>
<button class="btn">↻ Обновить</button>
</div>
<div class="filterbar">
<span class="fld search">🔍 Поиск: имя, телефон…</span>
<span class="fld">Статус <span class="caret"></span></span>
<span class="fld">Проект <span class="caret"></span></span>
<span class="fld">Источник <span class="caret"></span></span>
<span class="fld">Ответственный <span class="caret"></span></span>
<span class="fld">Период <span class="caret"></span></span>
</div>
<div class="tbl-card">
<table id="tbl-a">
<thead><tr>
<th style="width:32px;"><input type="checkbox" /></th>
<th>Лид</th><th>Источник</th><th>Статус</th><th>Ответственный</th>
<th>Напоминание</th><th>Комментарий</th><th>Отгружен</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- ============ VARIANT B ============ -->
<section class="variant" id="v-b">
<div class="caption"><b>Вариант B — Паритет с поставщиком.</b> Точное зеркало страницы «Мои сделки» crm.bp: полный колоночный набор, звёздочка-избранное, ⋮-меню, <b>inline-редактирование напоминания и комментария прямо в строке</b>, задачные быстрофильтры, массовые «Присвоить статус» / «Установить комментарий». Операторы, знающие систему BG, сразу в ней ориентируются.</div>
<div class="page">
<div class="pagehead">
<div>
<h2>Мои сделки</h2>
<div class="stats"><span class="mono">128</span> сделок<span class="sep">·</span><span>показано 50</span></div>
</div>
</div>
<div class="filterbar">
<span class="fld">Статусы <span class="caret"></span></span>
<span class="fld">Проекты <span class="caret"></span></span>
<span class="fld">Ответственные <span class="caret"></span></span>
<span class="fld">Диапазон дат <span class="caret"></span></span>
</div>
<div class="taskchips">
<span class="taskchip on">Дела на сегодня <b>3</b></span>
<span class="taskchip">Просроченные <b>1</b></span>
<span class="taskchip">Предстоящие <b>5</b></span>
<span class="taskchip">Без задач <b>12</b></span>
</div>
<div class="bulkbar">
<span>Выбрано: <b>2</b></span>
<button class="btn">Присвоить статус ▾</button>
<button class="btn">Установить комментарий</button>
<span class="sp"></span>
<button class="btn ghost" style="color:#ffffff;">Снять выбор</button>
</div>
<div class="tbl-card">
<table id="tbl-b">
<thead><tr>
<th style="width:30px;"><input type="checkbox" /></th>
<th style="width:26px;"></th>
<th>Проект</th><th>Тег</th><th>Телефон</th><th>Ответственный</th>
<th>Эл. почта</th><th>Напоминание</th><th>Комментарий</th><th>Статус</th>
<th>Время отгрузки</th><th style="width:26px;"></th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</section>
<!-- ============ VARIANT C ============ -->
<section class="variant" id="v-c">
<div class="caption"><b>Вариант C — Лидерра-нативный инбокс.</b> Переосмысление под реселл-модель: переключатель <b>«Поток»</b> (хронологическая лента входящих лидов — видно, что лиды «капают» от поставщика) ↔ <b>«Воронка»</b> (канбан по стадиям). На карточке — крупный бейдж источника и цена лида (что списано тенанту). Лучше всего отражает «лиды по заказанным источникам», но это отдельный продуктовый заход.</div>
<div class="page">
<div class="pagehead">
<div>
<h2>Сделки</h2>
<div class="stats"><span><span class="num">+3</span> новых с утра</span><span class="sep">·</span><span><span class="num">41</span> в работе</span></div>
</div>
</div>
<div class="subtoggle" data-sub="c">
<button class="on" data-v="flow">⊞ Поток</button>
<button data-v="kanban">▦ Воронка</button>
</div>
<div class="subview on" id="c-flow"><div class="feed" id="feed-c"></div></div>
<div class="subview" id="c-kanban">
<div class="kanban" id="kanban-c"></div>
<div class="legend">Стадии группируют 14 статусов воронки. Карточки перетаскиваются между стадиями (drag &amp; drop).</div>
</div>
</div>
</section>
<!-- ============ HYBRID ============ -->
<section class="variant" id="v-h">
<div class="caption"><b>Гибрид — паритет-таблица B + фишки C.</b> Основа — таблица паритета с inline-напоминанием/комментарием и звёздочкой, но: имя+телефон объединены в одну колонку «Лид», источник вынесен крупным бейджем, добавлена колонка <b>«Цена»</b> (списано тенанту из <span class="mono">lead_charges</span>), и есть переключатель «Таблица ↔ Поток». Сохраняет привычность BG и добавляет портально-специфичное.</div>
<div class="page">
<div class="pagehead">
<div>
<h2>Сделки</h2>
<div class="stats"><span><span class="num">+3</span> новых с утра</span><span class="sep">·</span><span><span class="num">128</span> всего</span><span class="sep">·</span><span>списано за месяц <span class="num mono">38 940 ₽</span></span></div>
</div>
</div>
<div class="subtoggle" data-sub="h">
<button class="on" data-v="table">▤ Таблица</button>
<button data-v="flow">⊞ Поток</button>
</div>
<div class="filterbar">
<span class="fld search">🔍 Поиск</span>
<span class="fld">Статус <span class="caret"></span></span>
<span class="fld">Проект <span class="caret"></span></span>
<span class="fld">Источник <span class="caret"></span></span>
<span class="fld">Период <span class="caret"></span></span>
</div>
<div class="taskchips">
<span class="taskchip on">Сегодня <b>3</b></span>
<span class="taskchip">Просрочено <b>1</b></span>
<span class="taskchip">Без задач <b>12</b></span>
</div>
<div class="subview on" id="h-table">
<div class="tbl-card">
<table id="tbl-h">
<thead><tr>
<th style="width:30px;"><input type="checkbox" /></th>
<th style="width:26px;"></th>
<th>Лид</th><th>Источник</th><th>Статус</th><th>Ответственный</th>
<th>Напоминание</th><th>Комментарий</th><th>Цена</th><th>Отгружен</th><th style="width:26px;"></th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="subview" id="h-flow"><div class="feed" id="feed-h"></div></div>
</div>
</section>
</div>
<script>
/* ---- mock data: лиды, отгруженные поставщиком (хардкод-константы, не пользовательский ввод) ---- */
const LEADS = [
{name:'Алексей Громов', phone:'+7 916 234-56-78', project:'Окна-Люкс', tag:'OKNA', b:'B2', type:'Звонки', status:'Новый', resp:'А. Петрова', ri:'АП', email:'', reminder:'', comment:'', shipped:'12 мин назад', price:420},
{name:'Марина Ковалёва', phone:'+7 903 887-12-09', project:'Двери-Маркет', tag:'DVERI', b:'B1', type:'Сайт', status:'Переговоры', resp:'И. Сидоров', ri:'ИС', email:'m.kov@mail.ru', reminder:'сегодня 16:00', comment:'Перезвонить после обеда', shipped:'1 ч назад', price:380},
{name:'', phone:'+7 925 110-44-87', project:'Кухни-Про', tag:'KUHNI', b:'B3', type:'СМС', status:'Недозвон', resp:'А. Петрова', ri:'АП', email:'', reminder:'завтра 10:00', comment:'', shipped:'3 ч назад', price:260},
{name:'Сергей Власов', phone:'+7 999 002-77-31', project:'Окна-Люкс', tag:'OKNA', b:'B2', type:'Звонки', status:'Ожидаем оплаты', resp:'М. Орлов', ri:'МО', email:'', reminder:'', comment:'Счёт выставлен', shipped:'5 ч назад', price:420},
{name:'Гость #4471', phone:'+7 911 556-23-90', project:'Ремонт-Сервис', tag:'REM', b:'B1', type:'Сайт', status:'Горячий', resp:'И. Сидоров', ri:'ИС', email:'', reminder:'сегодня 12:30', comment:'Готов к замеру', shipped:'вчера', price:510},
{name:'Ольга Дроздова', phone:'+7 962 334-88-21', project:'Кухни-Про', tag:'KUHNI', b:'B3', type:'СМС', status:'Оплачено', resp:'М. Орлов', ri:'МО', email:'olga.d@gmail.com', reminder:'', comment:'', shipped:'вчера', price:260},
{name:'Павел Н.', phone:'+7 905 778-31-02', project:'Двери-Маркет', tag:'DVERI', b:'B1', type:'Сайт', status:'Просмотрено', resp:'', ri:'', email:'', reminder:'', comment:'', shipped:'вчера', price:380},
{name:'Ирина Соколова', phone:'+7 916 445-90-12', project:'Окна-Люкс', tag:'OKNA', b:'B2', type:'Звонки', status:'Закрыто и не реализовано', resp:'А. Петрова', ri:'АП', email:'', reminder:'', comment:'Дорого для клиента', shipped:'2 дн назад', price:420}
];
const ST = {
'Новый':['#e3f0ec','#0F6E56'], 'Просмотрено':['#e8ecf2','#4a5b73'], 'Проработан':['#e9e7f5','#4a3f8a'],
'База':['#ecebe6','#6b6356'], 'Недозвон':['#fbeede','#9a6b16'], 'Переговоры':['#e2ecf6','#2c5a86'],
'Ожидаем оплаты':['#fce8d8','#b3601a'], 'Партнёрка':['#f0e6f3','#7a3f86'], 'Оплачено':['#e0f0e2','#2f7d3a'],
'Закрыто и не реализовано':['#efe9e9','#8a5a5a'], 'Тест-драйв':['#e0f0f2','#2a7d86'], 'Горячий':['#fce4e2','#c0392b'],
'На замену':['#fbeede','#9a6b16'], 'Конечный недозвон':['#e8e6e3','#5a544c']
};
const BCOL = {B1:'#0F6E56', B2:'#c8821a', B3:'#6a4a9c'};
/* helpers — build DOM via createElement, без innerHTML */
function el(tag, cls, txt){ const e=document.createElement(tag); if(cls) e.className=cls; if(txt!=null) e.textContent=txt; return e; }
function pillEl(s){
const c = ST[s]||['#eee','#555'];
const e = el('span','pill',s);
e.style.background=c[0]; e.style.color=c[1];
return e;
}
function srcEl(l, big){
const e = el('span','src'+(big?' big':''));
const dot = el('span','dot'); dot.style.background=BCOL[l.b]; e.appendChild(dot);
e.appendChild(el('span','b',l.b));
e.appendChild(el('span','ty','· '+l.type));
return e;
}
function avaEl(l, sm){
const ini = (l.name||'?').replace('Гость #','Г').split(' ').map(p=>p[0]).join('').slice(0,2).toUpperCase();
return el('span','ava'+(sm?' sm':''), ini);
}
function nameOf(l){ return l.name || 'Без имени'; }
function respCellEl(l){
if(!l.resp) return el('span','inline','—');
const w = el('span','rem'); w.appendChild(avaEl(l,true)); w.appendChild(document.createTextNode(' '+l.resp));
return w;
}
function remEditEl(l){
if(l.reminder){
const due = /сегодня|завтра/.test(l.reminder);
return el('span','rem'+(due?' due':''),'⏰ '+l.reminder);
}
return el('span','inline-edit','+ напоминание');
}
function cmtEditEl(l){
return l.comment ? el('span','inline set',l.comment) : el('span','inline-edit','+ комментарий');
}
function td(child){ const c=el('td'); if(child!=null){ child instanceof Node ? c.appendChild(child) : (c.textContent=child); } return c; }
function cell(){ const c=el('td'); for(const n of arguments){ if(n!=null) c.appendChild(n instanceof Node?n:document.createTextNode(n)); } return c; }
function checkbox(checked){ const i=el('input'); i.type='checkbox'; if(checked) i.checked=true; return i; }
function starEl(on){ return el('span','star'+(on?' on':''), on?'★':'☆'); }
function leadCell(l){
const w=el('div','lead'); w.appendChild(avaEl(l));
const d=el('div'); d.appendChild(el('div','nm',nameOf(l))); d.appendChild(el('div','ph mono',l.phone));
w.appendChild(d); return w;
}
/* ---- variant A table ---- */
(function(){
const tb=document.querySelector('#tbl-a tbody');
LEADS.forEach(l=>{
const tr=el('tr','row');
tr.appendChild(td(checkbox()));
tr.appendChild(td(leadCell(l)));
const srcTd=el('td'); srcTd.appendChild(el('span','inline set',l.project)); srcTd.appendChild(el('br')); srcTd.appendChild(srcEl(l)); tr.appendChild(srcTd);
tr.appendChild(td(pillEl(l.status)));
tr.appendChild(td(respCellEl(l)));
tr.appendChild(td(l.reminder?el('span','rem due','⏰ '+l.reminder):el('span','inline','—')));
tr.appendChild(td(l.comment?el('span','inline set',l.comment):el('span','inline','—')));
tr.appendChild(td(el('span','inline',l.shipped)));
tb.appendChild(tr);
});
})();
/* ---- variant B table (паритет) ---- */
(function(){
const tb=document.querySelector('#tbl-b tbody');
LEADS.forEach((l,i)=>{
const tr=el('tr','row');
tr.appendChild(td(checkbox(i<2)));
tr.appendChild(td(starEl(i===4)));
tr.appendChild(td(el('span','inline set',l.project)));
tr.appendChild(td(el('span','mono',l.tag)));
const ph=el('td'); ph.className='mono'; ph.textContent=l.phone; tr.appendChild(ph);
tr.appendChild(td(respCellEl(l)));
tr.appendChild(td(l.email?el('span','inline set',l.email):el('span','inline','—')));
tr.appendChild(td(remEditEl(l)));
tr.appendChild(td(cmtEditEl(l)));
tr.appendChild(td(pillEl(l.status)));
tr.appendChild(td(el('span','inline',l.shipped)));
tr.appendChild(td(el('span','dots','⋮')));
tb.appendChild(tr);
});
})();
/* ---- hybrid table ---- */
(function(){
const tb=document.querySelector('#tbl-h tbody');
LEADS.forEach((l,i)=>{
const tr=el('tr','row');
tr.appendChild(td(checkbox()));
tr.appendChild(td(starEl(i===4)));
tr.appendChild(td(leadCell(l)));
const srcTd=el('td');
const pj=el('div',null,l.project); pj.style.cssText='font-size:12px;margin-bottom:3px;color:var(--muted)';
srcTd.appendChild(pj); srcTd.appendChild(srcEl(l)); tr.appendChild(srcTd);
tr.appendChild(td(pillEl(l.status)));
tr.appendChild(td(respCellEl(l)));
tr.appendChild(td(remEditEl(l)));
tr.appendChild(td(cmtEditEl(l)));
tr.appendChild(td(el('span','price',l.price+' ₽')));
tr.appendChild(td(el('span','inline',l.shipped)));
tr.appendChild(td(el('span','dots','⋮')));
tb.appendChild(tr);
});
})();
/* ---- flow feed (C + hybrid) ---- */
function buildFeed(target){
LEADS.forEach(l=>{
const card=el('div','lcard');
card.appendChild(el('div','when mono',l.shipped));
card.appendChild(srcEl(l,true));
const main=el('div','main');
main.appendChild(el('div','nm',nameOf(l)));
main.appendChild(el('div','ph mono',l.phone+' · '+l.project));
card.appendChild(main);
const meta=el('div','meta');
if(l.reminder) meta.appendChild(el('span','rem due','⏰ '+l.reminder));
meta.appendChild(el('span','price',l.price+' ₽'));
meta.appendChild(pillEl(l.status));
meta.appendChild(l.resp?avaEl(l,true):el('span','inline','—'));
card.appendChild(meta);
target.appendChild(card);
});
}
buildFeed(document.getElementById('feed-c'));
buildFeed(document.getElementById('feed-h'));
/* ---- kanban (C) ---- */
const STAGES = [
['Новые',['Новый','Просмотрено']],
['В работе',['Проработан','База','Недозвон']],
['Переговоры',['Переговоры','Горячий','Тест-драйв']],
['Оплата',['Ожидаем оплаты','Оплачено','Партнёрка']],
['Закрыто',['Закрыто и не реализовано','Конечный недозвон','На замену']]
];
(function(){
const k=document.getElementById('kanban-c');
STAGES.forEach(([title,sts])=>{
const cards=LEADS.filter(l=>sts.includes(l.status));
const col=el('div','kcol');
const h=el('h4'); h.appendChild(document.createTextNode(title+' ')); h.appendChild(el('span',null,String(cards.length)));
col.appendChild(h);
const body=el('div','body');
if(!cards.length) body.appendChild(el('span','inline','пусто'));
cards.forEach(l=>{
const kc=el('div','kcard');
kc.appendChild(el('div','nm',nameOf(l)));
kc.appendChild(el('div','ph mono',l.phone));
const ft=el('div','ft'); ft.appendChild(srcEl(l)); ft.appendChild(el('span','price',l.price+' ₽'));
kc.appendChild(ft); body.appendChild(kc);
});
col.appendChild(body); k.appendChild(col);
});
})();
/* ---- tab switching ---- */
document.querySelectorAll('.tab').forEach(t=>t.addEventListener('click',()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.variant').forEach(x=>x.classList.remove('active'));
t.classList.add('active');
document.getElementById('v-'+t.dataset.tab).classList.add('active');
window.scrollTo({top:0,behavior:'smooth'});
}));
/* ---- sub-toggles (C: flow/kanban, H: table/flow) ---- */
document.querySelectorAll('.subtoggle').forEach(grp=>{
const sub=grp.dataset.sub;
grp.querySelectorAll('button').forEach(b=>b.addEventListener('click',()=>{
grp.querySelectorAll('button').forEach(x=>x.classList.remove('on'));
b.classList.add('on');
grp.parentElement.querySelectorAll('.subview').forEach(v=>v.classList.remove('on'));
document.getElementById(sub+'-'+b.dataset.v).classList.add('on');
}));
});
</script>
</body>
</html>