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>
315 lines
19 KiB
HTML
315 lines
19 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Сделки — финальный дизайн · Лидерра</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:23px;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{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:16px 20px;margin-bottom:16px;}
|
||
.intro p{margin:5px 0;color:var(--muted);font-size:13px;}
|
||
.intro b{color:var(--ink);}
|
||
.intro ul{margin:6px 0;padding-left:20px;color:var(--muted);font-size:13px;}
|
||
.note{background:#fff8ec;border:1px solid #ecdcb8;border-left:3px solid #c8821a;border-radius:6px;padding:10px 14px;margin-top:10px;color:#7a5a1a;font-size:12.5px;}
|
||
|
||
.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 13px;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);}
|
||
|
||
/* export panel */
|
||
.export{
|
||
display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--surface);
|
||
border:1px solid var(--line);border-radius:var(--radius);padding:11px 14px;margin-bottom:12px;
|
||
}
|
||
.export .lbl{font-size:13px;color:var(--muted);}
|
||
.export input[type="date"]{
|
||
border:1px solid var(--line);border-radius:7px;padding:6px 9px;font:inherit;font-size:13px;color:var(--ink);
|
||
}
|
||
.export .dash{color:var(--muted);}
|
||
.export .sp{flex:1;}
|
||
.export .ok{font-size:12px;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:220px;flex:1;}
|
||
.fld .caret{opacity:.5;font-size:10px;}
|
||
.perpage{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--muted);margin-bottom:12px;}
|
||
.pp-btn{border:1px solid var(--line);background:var(--surface);border-radius:6px;padding:4px 10px;cursor:pointer;font:inherit;font-size:13px;color:var(--muted);}
|
||
.pp-btn.on{background:var(--teal);color:#ffffff;border-color:var(--teal);}
|
||
|
||
/* master-detail body: список + панель сделки сбоку */
|
||
.deals-body{display:flex;gap:14px;align-items:flex-start;}
|
||
.deals-body .tbl-card{flex:1;min-width:0;}
|
||
|
||
/* table */
|
||
.tbl-card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow-x:auto;}
|
||
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;white-space:nowrap;}
|
||
td{padding:11px 12px;border-bottom:1px solid var(--line);vertical-align:middle;}
|
||
tr.row{cursor:pointer;}
|
||
tr.row:hover{background:#fbfaf6;}
|
||
tr.row.selected{background:rgba(15,110,86,.07);}
|
||
.phone{font-family:'JetBrains Mono',monospace;font-weight:500;}
|
||
.proj{font-weight:500;}
|
||
.ty{font-size:12px;color:var(--muted);}
|
||
.city{font-size:13px;}
|
||
.inline{font-size:12px;color:var(--muted);}
|
||
.inline.set{color:var(--ink);font-size:13px;}
|
||
.rem{display:inline-flex;align-items:center;gap:5px;font-size:12px;}
|
||
.rem.due{color:#9a6b16;}
|
||
.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;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;}
|
||
|
||
/* панель сделки («легенда») — справа, не поверх списка */
|
||
.detail-panel{width:360px;flex-shrink:0;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);display:none;}
|
||
.detail-panel.open{display:block;}
|
||
.dp-head{display:flex;justify-content:space-between;align-items:center;padding:13px 16px;border-bottom:1px solid var(--line);}
|
||
.dp-head .t{font-weight:600;font-size:15px;}
|
||
.dp-close{background:none;border:0;font-size:16px;cursor:pointer;color:var(--muted);line-height:1;}
|
||
.dp-body{padding:14px 16px;}
|
||
.dp-phone{font-family:'JetBrains Mono',monospace;font-size:17px;font-weight:600;margin-bottom:8px;}
|
||
.dp-status{margin-bottom:12px;}
|
||
.dp-row{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px;}
|
||
.dp-row .k{color:var(--muted);flex-shrink:0;}
|
||
.dp-row .v{text-align:right;}
|
||
.dp-hist{margin-top:14px;}
|
||
.dp-hist .hh{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:7px;}
|
||
.dp-hist .ev{font-size:12px;color:var(--muted);padding:3px 0 3px 10px;border-left:2px solid var(--line);margin-bottom:4px;}
|
||
|
||
/* footer / pagination */
|
||
.tfoot{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex-wrap:wrap;padding:12px 4px 0;}
|
||
.pager{display:flex;align-items:center;gap:4px;}
|
||
.pg{border:1px solid var(--line);background:var(--surface);border-radius:6px;min-width:30px;height:30px;cursor:pointer;font:inherit;font-size:13px;color:var(--ink);}
|
||
.pg.on{background:var(--noir);color:#ffffff;border-color:var(--noir);}
|
||
.pg:disabled{opacity:.4;cursor:default;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<div class="intro">
|
||
<h1>Страница «Сделки» — финальный дизайн</h1>
|
||
<p>Вариант A с правками. Лиды поступают только от поставщика crm.bp по заказанным в проектах источникам; ручного создания и корзины нет.</p>
|
||
<ul>
|
||
<li>Колонка <b>Телефон</b> — только номер (ФИО убрано).</li>
|
||
<li>Колонка <b>Источник</b> — проект + тип сигнала; B1/B2/B3 не показываются.</li>
|
||
<li><b>5 статусов:</b> Новая сделка → Просмотрено → В работе → Сделка / Не реализовано.</li>
|
||
<li>Колонка «Ответственный» убрана; «Отгружен» → <b>«Поставлен»</b>.</li>
|
||
<li>Добавлена колонка <b>Город</b>.</li>
|
||
<li>Экспорт за период поставки в Excel и CSV; выбор числа строк 10 / 20 / 50.</li>
|
||
<li><b>Клик по строке</b> открывает панель сделки справа — список <b>сжимается влево</b>, панель не перекрывает его (повторный клик / ✕ — закрыть).</li>
|
||
</ul>
|
||
<div class="note">
|
||
<b>Город — источник данных не определён.</b> crm.bp город/регион не передаёт ни в вебхуке
|
||
(<span class="mono">SupplierWebhookController</span> принимает только vid/project/phone/time/tag/phones),
|
||
ни в CSV-импорте; доработку поставщик делать не будет. Источник заполнения колонки заказчик
|
||
определит отдельно — вопрос записан, напоминание на 18.05. В макете — мок-данные.
|
||
</div>
|
||
<div class="note" style="background:#eef4f1;border-color:#cfe0d9;border-left-color:var(--teal);color:#2a5145;">
|
||
<b>Каскад от 5 статусов.</b> Сжатие воронки 14 → 5 статусов потянет за собой: <span class="mono">lead_statuses</span>/воронку в схеме, стор <span class="mono">leadStatuses</span>, страницу <b>Канбан</b> (колонки), фильтр и массовую смену статуса, маппинг статусов из импорта поставщика. Это отдельный backend-эпик — здесь только макет.
|
||
</div>
|
||
</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">14</span> за период</span><span class="sep">·</span><span><span class="num">4</span> в работе</span></div>
|
||
</div>
|
||
<button class="btn">↻ Обновить</button>
|
||
</div>
|
||
|
||
<!-- Экспорт по датам поставки -->
|
||
<div class="export">
|
||
<span class="lbl">Поставлены с</span>
|
||
<input type="date" id="from" value="2026-05-10" />
|
||
<span class="dash">—</span>
|
||
<input type="date" id="to" value="2026-05-17" />
|
||
<button class="btn" id="exp-xlsx">⤓ Экспорт в Excel</button>
|
||
<button class="btn" id="exp-csv">⤓ Экспорт в CSV</button>
|
||
<span class="sp"></span>
|
||
<span class="ok" id="exp-msg"></span>
|
||
</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>
|
||
</div>
|
||
|
||
<div class="perpage">Показывать по:
|
||
<button class="pp-btn on" data-pp="10">10</button>
|
||
<button class="pp-btn" data-pp="20">20</button>
|
||
<button class="pp-btn" data-pp="50">50</button>
|
||
</div>
|
||
|
||
<!-- Список + панель сделки (master-detail) -->
|
||
<div class="deals-body">
|
||
<div class="tbl-card">
|
||
<table id="tbl">
|
||
<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>
|
||
|
||
<aside class="detail-panel" id="panel">
|
||
<div class="dp-head">
|
||
<span class="t">Сделка</span>
|
||
<button class="dp-close" id="panel-close" aria-label="Закрыть">✕</button>
|
||
</div>
|
||
<div class="dp-body" id="panel-body"></div>
|
||
</aside>
|
||
</div>
|
||
|
||
<div class="tfoot">
|
||
<div class="pager" id="pager"></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
/* лиды, поставленные crm.bp (мок-данные; город — из источника, который заказчик определит) */
|
||
const LEADS = [
|
||
{phone:'+7 916 234-56-78', project:'Окна-Люкс', type:'Звонки', city:'Москва', status:'Новая сделка', reminder:'', comment:'', shipped:'17.05 09:12'},
|
||
{phone:'+7 903 887-12-09', project:'Двери-Маркет', type:'Сайт', city:'Санкт-Петербург', status:'В работе', reminder:'сегодня 16:00', comment:'Перезвонить после обеда', shipped:'17.05 08:40'},
|
||
{phone:'+7 928 220-71-44', project:'Ремонт-Сервис',type:'Звонки', city:'Казань', status:'Новая сделка', reminder:'', comment:'', shipped:'17.05 10:05'},
|
||
{phone:'+7 925 110-44-87', project:'Кухни-Про', type:'СМС', city:'Подольск', status:'Просмотрено', reminder:'', comment:'', shipped:'16.05 19:20'},
|
||
{phone:'+7 999 002-77-31', project:'Окна-Люкс', type:'Звонки', city:'Краснодар', status:'Сделка', reminder:'', comment:'Счёт оплачен', shipped:'16.05 14:05'},
|
||
{phone:'+7 911 556-23-90', project:'Ремонт-Сервис',type:'Сайт', city:'Санкт-Петербург', status:'В работе', reminder:'сегодня 12:30', comment:'Готов к замеру', shipped:'16.05 11:50'},
|
||
{phone:'+7 962 334-88-21', project:'Кухни-Про', type:'СМС', city:'Ростов-на-Дону', status:'Не реализовано', reminder:'', comment:'Дорого для клиента', shipped:'15.05 16:30'},
|
||
{phone:'+7 905 778-31-02', project:'Двери-Маркет', type:'Сайт', city:'Екатеринбург', status:'Просмотрено', reminder:'', comment:'', shipped:'15.05 10:15'},
|
||
{phone:'+7 952 661-09-37', project:'Кухни-Про', type:'СМС', city:'Новосибирск', status:'В работе', reminder:'завтра 11:00', comment:'Ждёт КП', shipped:'14.05 09:30'},
|
||
{phone:'+7 916 445-90-12', project:'Окна-Люкс', type:'Звонки', city:'Москва', status:'Сделка', reminder:'', comment:'', shipped:'14.05 13:00'},
|
||
{phone:'+7 919 003-55-18', project:'Двери-Маркет', type:'Сайт', city:'Москва', status:'Не реализовано', reminder:'', comment:'Не тот регион', shipped:'13.05 18:45'},
|
||
{phone:'+7 909 871-22-60', project:'Окна-Люкс', type:'Звонки', city:'Химки', status:'В работе', reminder:'', comment:'', shipped:'13.05 12:10'},
|
||
{phone:'+7 977 445-83-21', project:'Ремонт-Сервис',type:'Сайт', city:'Сочи', status:'Просмотрено', reminder:'', comment:'', shipped:'12.05 15:25'},
|
||
{phone:'+7 913 556-90-04', project:'Кухни-Про', type:'СМС', city:'Новосибирск', status:'Сделка', reminder:'', comment:'Оплачено', shipped:'11.05 11:40'}
|
||
];
|
||
|
||
/* 5 статусов воронки */
|
||
const ST = {
|
||
'Новая сделка':['#e3f0ec','#0F6E56'],
|
||
'Просмотрено':['#e8ecf2','#4a5b73'],
|
||
'В работе':['#fbeede','#9a6b16'],
|
||
'Сделка':['#e0f0e2','#2f7d3a'],
|
||
'Не реализовано':['#efe9e9','#9a5a52']
|
||
};
|
||
|
||
function el(tag,cls,txt){const e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e;}
|
||
function td(child){const c=el('td');if(child!=null)child instanceof Node?c.appendChild(child):(c.textContent=child);return c;}
|
||
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;}
|
||
|
||
let perPage=10, page=1;
|
||
|
||
/* панель сделки («легенда») */
|
||
function dpRow(k,v){
|
||
const r=el('div','dp-row');
|
||
r.appendChild(el('span','k',k));
|
||
r.appendChild(v instanceof Node?(()=>{const w=el('span','v');w.appendChild(v);return w;})():el('span','v',v));
|
||
return r;
|
||
}
|
||
function openPanel(l,rowEl){
|
||
document.querySelectorAll('tr.row.selected').forEach(r=>r.classList.remove('selected'));
|
||
if(rowEl)rowEl.classList.add('selected');
|
||
const body=document.getElementById('panel-body');
|
||
body.textContent='';
|
||
body.appendChild(el('div','dp-phone',l.phone));
|
||
body.appendChild((()=>{const d=el('div','dp-status');d.appendChild(pillEl(l.status));return d;})());
|
||
body.appendChild(dpRow('Источник', l.project+' · '+l.type));
|
||
body.appendChild(dpRow('Город', l.city));
|
||
body.appendChild(dpRow('Поставлен', l.shipped));
|
||
body.appendChild(dpRow('Напоминание', l.reminder||'—'));
|
||
body.appendChild(dpRow('Комментарий', l.comment||'—'));
|
||
const hist=el('div','dp-hist');
|
||
hist.appendChild(el('div','hh','История'));
|
||
hist.appendChild(el('div','ev','Поставлен от crm.bp — '+l.shipped));
|
||
hist.appendChild(el('div','ev','Статус: '+l.status));
|
||
if(l.comment)hist.appendChild(el('div','ev','Комментарий: '+l.comment));
|
||
body.appendChild(hist);
|
||
document.getElementById('panel').classList.add('open');
|
||
}
|
||
function closePanel(){
|
||
document.getElementById('panel').classList.remove('open');
|
||
document.querySelectorAll('tr.row.selected').forEach(r=>r.classList.remove('selected'));
|
||
}
|
||
document.getElementById('panel-close').addEventListener('click',closePanel);
|
||
|
||
function render(){
|
||
const total=LEADS.length, pages=Math.max(1,Math.ceil(total/perPage));
|
||
if(page>pages)page=pages;
|
||
const slice=LEADS.slice((page-1)*perPage, (page-1)*perPage+perPage);
|
||
|
||
const tb=document.querySelector('#tbl tbody');
|
||
tb.textContent='';
|
||
slice.forEach(l=>{
|
||
const tr=el('tr','row');
|
||
const cb=document.createElement('input');cb.type='checkbox';
|
||
cb.addEventListener('click',e=>e.stopPropagation());
|
||
tr.appendChild(td(cb));
|
||
tr.appendChild(td(el('span','phone',l.phone)));
|
||
const srcTd=el('td');
|
||
srcTd.appendChild(el('div','proj',l.project));
|
||
srcTd.appendChild(el('div','ty',l.type));
|
||
tr.appendChild(srcTd);
|
||
tr.appendChild(td(el('span','city',l.city)));
|
||
tr.appendChild(td(pillEl(l.status)));
|
||
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)));
|
||
tr.addEventListener('click',()=>openPanel(l,tr));
|
||
tb.appendChild(tr);
|
||
});
|
||
|
||
const pager=document.getElementById('pager');
|
||
pager.textContent='';
|
||
const prev=el('button','pg','‹');prev.disabled=page===1;prev.onclick=()=>{page--;render();};
|
||
pager.appendChild(prev);
|
||
for(let p=1;p<=pages;p++){
|
||
const b=el('button','pg'+(p===page?' on':''),String(p));
|
||
b.onclick=()=>{page=p;render();};
|
||
pager.appendChild(b);
|
||
}
|
||
const next=el('button','pg','›');next.disabled=page===pages;next.onclick=()=>{page++;render();};
|
||
pager.appendChild(next);
|
||
}
|
||
|
||
document.querySelectorAll('.pp-btn').forEach(b=>b.addEventListener('click',()=>{
|
||
document.querySelectorAll('.pp-btn').forEach(x=>x.classList.remove('on'));
|
||
b.classList.add('on');
|
||
perPage=Number(b.dataset.pp); page=1; render();
|
||
}));
|
||
|
||
function expMsg(fmt){
|
||
const from=document.getElementById('from').value, to=document.getElementById('to').value;
|
||
document.getElementById('exp-msg').textContent='Сформирован '+fmt+' за '+from+' — '+to+' (макет)';
|
||
}
|
||
document.getElementById('exp-xlsx').addEventListener('click',()=>expMsg('Excel'));
|
||
document.getElementById('exp-csv').addEventListener('click',()=>expMsg('CSV'));
|
||
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|