Files
portal/web/admin-dashboard-mockup.html
T
Дмитрий 1fd56e205b
Accessibility (Pa11y live) / a11y (push) Has been cancelled
docs(админка): спецификация + кликабельный макет «Командного центра»
Иерархический дашборд (3 уровня, drill-down). Этап 1: Командный центр +
Финансы + Здоровье (переиспользуют существующие экраны как L3). Этап 2: Лиды +
Заказ у поставщика. Механизм заказа задокументирован по коду (формула
SupplierQuotaAllocator: max(max_спрос, ceil(Σ/3))), без маржи (по решению владельца).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:37:28 +03:00

300 lines
21 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.0">
<title>Лидерра — Командный центр (макет)</title>
<style>
:root{
--teal:#0F6E56; --teal-d:#0a5443; --ivory:#F6F3EC; --noir:#012019;
--ink:#11201b; --muted:#6b7d76; --line:#e3ddd0; --card:#ffffff;
--green:#1f9d6b; --amber:#d4972a; --red:#cf4b46; --blue:#2f7fb5;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,Segoe UI,Roboto,sans-serif;background:var(--ivory);color:var(--ink);display:flex;min-height:100vh}
.num{font-family:'JetBrains Mono','Consolas',monospace;font-variant-numeric:tabular-nums}
/* sidebar */
.side{width:230px;background:var(--noir);color:#cfe3db;flex-shrink:0;padding:0 0 20px}
.brand{padding:20px 22px 16px;font-weight:800;font-size:20px;color:#ffffff}
.brand b{color:#7fe0bf}
.brand .tag{display:block;font-size:11px;letter-spacing:3px;color:#5f8d7e;font-weight:600;margin-top:2px}
.nav a{display:flex;gap:10px;align-items:center;padding:10px 22px;color:#a9c6bb;text-decoration:none;font-size:14px;border-left:3px solid transparent}
.nav a.act{background:#0a2b22;color:#ffffff;border-left-color:#7fe0bf;font-weight:600}
.nav a:hover{background:#08241d;color:#ffffff}
.nav .sec{font-size:10px;letter-spacing:2px;color:#445566;padding:14px 22px 6px;text-transform:uppercase}
/* main */
.main{flex:1;min-width:0;display:flex;flex-direction:column}
.top{display:flex;align-items:center;justify-content:space-between;padding:16px 28px;border-bottom:1px solid var(--line);background:#ffffff}
.crumb{font-size:13px;color:var(--muted)}
.crumb b{color:var(--ink);font-size:18px;display:block}
.period{display:flex;gap:4px;background:var(--ivory);border:1px solid var(--line);border-radius:9px;padding:3px}
.period button{border:0;background:transparent;padding:6px 14px;border-radius:6px;font-size:13px;cursor:pointer;color:var(--muted)}
.period button.on{background:var(--teal);color:#ffffff;font-weight:600}
.user{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:600}
.ava{width:30px;height:30px;border-radius:50%;background:var(--red);color:#ffffff;display:grid;place-items:center;font-size:12px}
.wrap{padding:24px 28px;max-width:1200px}
.hint{font-size:13px;color:var(--muted);margin-bottom:16px}
.hint b{color:var(--teal)}
/* tiles */
.tiles{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.tile{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:20px;cursor:pointer;transition:.15s;position:relative;box-shadow:0 1px 2px rgba(0,0,0,.03)}
.tile:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(15,110,86,.12);border-color:#bbccdd}
.tile.sel{border-color:var(--teal);box-shadow:0 8px 24px rgba(15,110,86,.18)}
.tile h3{font-size:15px;display:flex;align-items:center;gap:8px;margin-bottom:14px}
.tile .ico{font-size:18px}
.light{margin-left:auto;font-size:12px;font-weight:700;padding:3px 10px;border-radius:20px}
.l-g{background:#e4f6ee;color:var(--green)} .l-a{background:#fbf0db;color:var(--amber)} .l-r{background:#fae3e2;color:var(--red)}
.rows{display:flex;flex-direction:column;gap:9px}
.row{display:flex;justify-content:space-between;align-items:baseline;font-size:14px}
.row .k{color:var(--muted)}
.row .v{font-weight:700}
.row .v.big{font-size:22px}
.row .v.r{color:var(--red)} .row .v.gr{color:var(--green)}
.more{margin-top:14px;font-size:12px;color:var(--teal);font-weight:600}
/* drill */
.drill{margin-top:22px;background:#ffffff;border:1px solid var(--line);border-radius:16px;padding:0;overflow:hidden;display:none}
.drill.show{display:block}
.dhead{padding:16px 22px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;background:#fbfaf6}
.dhead .t{font-weight:700;font-size:16px}
.dhead .bk{margin-left:auto;font-size:12px;color:var(--muted)}
.dbody{padding:22px}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:22px}
.kpi{background:var(--ivory);border-radius:12px;padding:14px}
.kpi .lab{font-size:12px;color:var(--muted)}
.kpi .val{font-size:20px;font-weight:800;margin-top:4px}
.kpi .val.r{color:var(--red)} .kpi .val.gr{color:var(--green)}
.grid2{display:grid;grid-template-columns:1.3fr 1fr;gap:22px}
.panel h4{font-size:13px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin-bottom:12px}
/* bar chart */
.chart{display:flex;align-items:flex-end;gap:8px;height:130px;padding:6px 0;border-bottom:1px solid var(--line)}
.bar{flex:1;display:flex;flex-direction:column;justify-content:flex-end;gap:3px;align-items:center}
.bar .b1{width:60%;background:var(--teal);border-radius:4px 4px 0 0}
.bar .b2{width:60%;background:#e0b15a;border-radius:4px 4px 0 0}
.bar .bl{font-size:10px;color:var(--muted)}
.leg{display:flex;gap:16px;font-size:12px;color:var(--muted);margin-top:8px}
.leg i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:5px;vertical-align:middle}
table{width:100%;border-collapse:collapse;font-size:13px}
th{text-align:left;color:var(--muted);font-weight:600;font-size:11px;text-transform:uppercase;padding:8px 8px;border-bottom:1px solid var(--line)}
td{padding:10px 8px;border-bottom:1px solid #f0ece2}
tr.clk:hover{background:#f6fbf9;cursor:pointer}
.pill{font-size:11px;padding:2px 9px;border-radius:20px;font-weight:600}
.p-ok{background:#e4f6ee;color:var(--green)} .p-warn{background:#fbf0db;color:var(--amber)} .p-bad{background:#fae3e2;color:var(--red)}
/* health subsystems */
.subs{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
.sub{border:1px solid var(--line);border-radius:12px;padding:14px;display:flex;flex-direction:column;gap:6px}
.sub .nm{font-weight:600;font-size:14px;display:flex;align-items:center;gap:8px}
.dot{width:10px;height:10px;border-radius:50%}
.d-g{background:var(--green)} .d-a{background:var(--amber)} .d-r{background:var(--red)}
.sub .meta{font-size:12px;color:var(--muted)}
.soon{display:inline-block;font-size:11px;background:#eeeeee;color:#888888;padding:2px 8px;border-radius:10px;margin-left:6px}
.foot{padding:14px 28px;color:#99aaaa;font-size:11px;border-top:1px solid var(--line);margin-top:auto}
</style>
</head>
<body>
<aside class="side">
<div class="brand">Лидерра.<span class="tag">ADMIN</span></div>
<nav class="nav">
<a class="act" href="#">📊 Командный центр</a>
<div class="sec">Данные</div>
<a href="#" onclick="open_('fin');return false">👥 Тенанты</a>
<a href="#" onclick="open_('fin');return false">💳 Биллинг</a>
<a href="#">🏷️ Тарифная сетка</a>
<a href="#">₽ Цены поставщиков</a>
<div class="sec">Эксплуатация</div>
<a href="#" onclick="open_('health');return false">⚠️ Инциденты</a>
<a href="#">🧑‍🤝‍🧑 Impersonation</a>
<a href="#">⚙️ Система</a>
<a href="#" onclick="open_('health');return false">🔁 Интеграция с поставщиком</a>
<a href="#">🗂️ Проекты у поставщика</a>
<a href="#">🛡️ Обращения ПДн</a>
</nav>
</aside>
<div class="main">
<div class="top">
<div class="crumb">Админка<b>Командный центр</b></div>
<div class="period">
<button>Сегодня</button><button class="on">7 дней</button><button>30 дней</button>
</div>
<div class="user"><span class="ava">ДК</span> Дмитрий К.</div>
</div>
<div class="wrap">
<div class="hint">👆 Это <b>макет</b>. Кликните любую плитку — провалитесь в детали (Уровень 2). Цифры — для примера.</div>
<div class="tiles">
<!-- ФИНАНСЫ -->
<div class="tile sel" id="t-fin" onclick="open_('fin')">
<h3><span class="ico">💰</span> Финансы <span class="light l-r">1 в минусе</span></h3>
<div class="rows">
<div class="row"><span class="k">Пополнения за 7 дней</span><span class="v big num">320 000 ₽</span></div>
<div class="row"><span class="k">Списано за лиды</span><span class="v num">180 000 ₽</span></div>
<div class="row"><span class="k">Активных клиентов</span><span class="v num">5</span></div>
<div class="row"><span class="k">Новых за 7 дней</span><span class="v gr num">+2</span></div>
</div>
<div class="more">Открыть финансы →</div>
</div>
<!-- ЗДОРОВЬЕ -->
<div class="tile" id="t-health" onclick="open_('health')">
<h3><span class="ico">❤️</span> Здоровье портала <span class="light l-g">🟢 OK</span></h3>
<div class="rows">
<div class="row"><span class="k">Очереди / джобы</span><span class="v gr">работают</span></div>
<div class="row"><span class="k">Синхрон с поставщиком</span><span class="v gr">вчера 00:05 ✓</span></div>
<div class="row"><span class="k">Открытых инцидентов</span><span class="v num">0</span></div>
<div class="row"><span class="k">Последний сбой</span><span class="v">нет</span></div>
</div>
<div class="more">Открыть здоровье →</div>
</div>
<!-- ЛИДЫ -->
<div class="tile" id="t-leads" onclick="open_('leads')">
<h3><span class="ico">🎯</span> Лиды <span class="light l-g">🟢 чисто</span></h3>
<div class="rows">
<div class="row"><span class="k">Доставлено сегодня</span><span class="v big num">71</span></div>
<div class="row"><span class="k">Зависших</span><span class="v num">0</span></div>
<div class="row"><span class="k">Нераспределённых</span><span class="v num">0</span></div>
<div class="row"><span class="k">% доставки</span><span class="v gr num">100%</span></div>
</div>
<div class="more">Этап 2 →</div>
</div>
<!-- ЗАКАЗ У ПОСТАВЩИКА -->
<div class="tile" id="t-supply" onclick="open_('supply')">
<h3><span class="ico">📦</span> Заказ у поставщика <span class="light l-r">🔴 1 рассинхрон</span></h3>
<div class="rows">
<div class="row"><span class="k">Клиенты просят (Σ/день)</span><span class="v big num">250</span></div>
<div class="row"><span class="k">Надо по формуле</span><span class="v num">160</span></div>
<div class="row"><span class="k">Заказали по факту</span><span class="v num">175</span></div>
<div class="row"><span class="k">Групп с рассинхроном</span><span class="v r num">1</span></div>
</div>
<div class="more">Этап 2 →</div>
</div>
</div>
<!-- DRILL: ФИНАНСЫ -->
<div class="drill show" id="d-fin">
<div class="dhead"><span>💰</span><span class="t">Финансы — детали</span><span class="bk">← клик по плитке возвращает наверх</span></div>
<div class="dbody">
<div class="kpis">
<div class="kpi"><div class="lab">Пополнения (7д)</div><div class="val">320 000 ₽</div></div>
<div class="kpi"><div class="lab">Списано за лиды (7д)</div><div class="val">180 000 ₽</div></div>
<div class="kpi"><div class="lab">Чистый приток</div><div class="val gr">+140 000 ₽</div></div>
<div class="kpi"><div class="lab">Клиентов в минусе</div><div class="val r">1</div></div>
</div>
<div class="grid2">
<div class="panel">
<h4>Пополнения и списания по дням</h4>
<div class="chart">
<div class="bar"><div class="b1" style="height:55px"></div><div class="b2" style="height:30px"></div><div class="bl">Пн</div></div>
<div class="bar"><div class="b1" style="height:40px"></div><div class="b2" style="height:35px"></div><div class="bl">Вт</div></div>
<div class="bar"><div class="b1" style="height:80px"></div><div class="b2" style="height:45px"></div><div class="bl">Ср</div></div>
<div class="bar"><div class="b1" style="height:30px"></div><div class="b2" style="height:50px"></div><div class="bl">Чт</div></div>
<div class="bar"><div class="b1" style="height:95px"></div><div class="b2" style="height:40px"></div><div class="bl">Пт</div></div>
<div class="bar"><div class="b1" style="height:20px"></div><div class="b2" style="height:25px"></div><div class="bl">Сб</div></div>
<div class="bar"><div class="b1" style="height:60px"></div><div class="b2" style="height:38px"></div><div class="bl">Вс</div></div>
</div>
<div class="leg"><span><i style="background:#0F6E56"></i>Пополнения</span><span><i style="background:#e0b15a"></i>Списания</span></div>
</div>
<div class="panel">
<h4>🔴 Требуют внимания (баланс кончается)</h4>
<table>
<tr><th>Клиент</th><th>Баланс</th><th>Хватит на</th></tr>
<tr class="clk"><td>ООО «Ромашка»</td><td class="num">4 200 ₽</td><td><span class="pill p-bad">в минусе</span></td></tr>
<tr class="clk"><td>ИП Сидоров</td><td class="num">3 100 ₽</td><td><span class="pill p-warn">~1 день</span></td></tr>
<tr class="clk"><td>ООО «Вектор»</td><td class="num">18 900 ₽</td><td><span class="pill p-warn">~3 дня</span></td></tr>
</table>
<h4 style="margin-top:18px">Топ по обороту (7д)</h4>
<table>
<tr><th>Клиент</th><th>Пополнил</th><th>Лидов</th></tr>
<tr class="clk"><td>lkomega</td><td class="num">200 000 ₽</td><td class="num">712</td></tr>
<tr class="clk"><td>ООО «Вектор»</td><td class="num">80 000 ₽</td><td class="num">190</td></tr>
<tr class="clk"><td>ИП Сидоров</td><td class="num">40 000 ₽</td><td class="num">85</td></tr>
</table>
</div>
</div>
</div>
</div>
<!-- DRILL: ЗДОРОВЬЕ -->
<div class="drill" id="d-health">
<div class="dhead"><span>❤️</span><span class="t">Здоровье портала — детали</span><span class="bk">зелёное — всё ок</span></div>
<div class="dbody">
<div class="subs">
<div class="sub"><div class="nm"><span class="dot d-g"></span>Очереди / джобы</div><div class="meta">0 упавших за сутки · обрабатываются</div></div>
<div class="sub"><div class="nm"><span class="dot d-g"></span>Планировщик</div><div class="meta">все задачи по расписанию · посл. 00:05</div></div>
<div class="sub"><div class="nm"><span class="dot d-g"></span>Синхрон с поставщиком</div><div class="meta">вчера ✓ · 12 групп, 12 ок</div></div>
<div class="sub"><div class="nm"><span class="dot d-g"></span>Сверка CSV (дрейф)</div><div class="meta">дрейф 0% · мусора 56 (норм)</div></div>
<div class="sub"><div class="nm"><span class="dot d-a"></span>Вебхуки</div><div class="meta">1 неразобранный · посмотреть →</div></div>
<div class="sub"><div class="nm"><span class="dot d-g"></span>Инциденты</div><div class="meta">0 открытых</div></div>
</div>
<div style="margin-top:20px" class="panel">
<h4>Последние события эксплуатации</h4>
<table>
<tr><th>Когда</th><th>Что</th><th>Статус</th></tr>
<tr class="clk"><td class="num">сегодня 00:05</td><td>Вечерняя заливка поставщику</td><td><span class="pill p-ok">успех</span></td></tr>
<tr class="clk"><td class="num">сегодня 03:30</td><td>Резервная копия БД</td><td><span class="pill p-ok">успех</span></td></tr>
<tr class="clk"><td class="num">вчера 14:12</td><td>Сверка резервного канала (CSV)</td><td><span class="pill p-ok">дрейф 0%</span></td></tr>
</table>
</div>
</div>
</div>
<!-- DRILL: ЛИДЫ (phase 2) -->
<div class="drill" id="d-leads">
<div class="dhead"><span>🎯</span><span class="t">Лиды — детали</span><span class="bk soon">Этап 2</span></div>
<div class="dbody">
<div class="kpis">
<div class="kpi"><div class="lab">Доставлено сегодня</div><div class="val">71</div></div>
<div class="kpi"><div class="lab">Зависших</div><div class="val gr">0</div></div>
<div class="kpi"><div class="lab">Нераспределённых</div><div class="val gr">0</div></div>
<div class="kpi"><div class="lab">Утечка (закуплено−доставлено)</div><div class="val gr">0</div></div>
</div>
<p style="color:var(--muted);font-size:13px">Здесь будет: распределение лидов по клиентам, поиск зависших/потерянных, % доставки по дням. Делаем на Этапе 2.</p>
</div>
</div>
<!-- DRILL: ЗАКАЗ У ПОСТАВЩИКА (phase 2) -->
<div class="drill" id="d-supply">
<div class="dhead"><span>📦</span><span class="t">Заказ у поставщика — детали</span><span class="bk soon">Этап 2</span></div>
<div class="dbody">
<div class="kpis">
<div class="kpi"><div class="lab">Клиенты просят (Σ/день)</div><div class="val">250</div></div>
<div class="kpi"><div class="lab">Надо по формуле</div><div class="val">160</div></div>
<div class="kpi"><div class="lab">Заказали по факту</div><div class="val">175</div></div>
<div class="kpi"><div class="lab">Рассинхронов</div><div class="val r">1</div></div>
</div>
<div class="panel">
<h4>По группам/проектам: спрос → формула → факт</h4>
<table>
<tr><th>Группа / проект</th><th>Клиенты просят (Σ)</th><th>Надо по формуле</th><th>Заказали по факту</th><th>Совпадает?</th></tr>
<tr class="clk"><td>Окна Москва</td><td class="num">150</td><td class="num">100</td><td class="num">100</td><td><span class="pill p-ok">🟢 да</span></td></tr>
<tr class="clk"><td>Кредиты СПб</td><td class="num">40</td><td class="num">40</td><td class="num">40</td><td><span class="pill p-ok">🟢 да</span></td></tr>
<tr class="clk"><td>Авто Казань</td><td class="num">60</td><td class="num">20</td><td class="num">35</td><td><span class="pill p-bad">🔴 рассинхрон</span></td></tr>
</table>
</div>
<p style="color:var(--muted);font-size:13px;margin-top:14px">Формула заказа: <b>max( самый крупный клиент ; ⌈сумма спроса ÷ 3⌉ )</b> — лид перепродаётся до 3 клиентов. 🔴 рассинхрон = «по факту» ≠ «по формуле» (правили лимит руками / синхрон 18:05 не прошёл). Делаем на Этапе 2.</p>
</div>
</div>
</div>
<div class="foot">Лидерра · Командный центр (макет для согласования вида) · цифры демонстрационные</div>
</div>
<script>
function open_(id){
['fin','health','leads','supply'].forEach(function(k){
document.getElementById('d-'+k).classList.toggle('show', k===id);
document.getElementById('t-'+k).classList.toggle('sel', k===id);
});
window.scrollTo({top:document.getElementById('d-'+id).offsetTop-20,behavior:'smooth'});
}
</script>
</body>
</html>