55a34af986
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>
507 lines
28 KiB
HTML
507 lines
28 KiB
HTML
<!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> — минимальная коррекция текущей таблицы · <b>B</b> — паритет с «Мои сделки» поставщика · <b>C</b> — Лидерра-нативный инбокс (Поток/Воронка) · <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 & 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>
|