203 lines
8.9 KiB
HTML
203 lines
8.9 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{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--red:#FF4757;--orange:#FFA502;--green:#2ED573}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
|
||
.container{max-width:1400px;margin:0 auto;padding:80px 24px}
|
||
.hero{background:var(--ink);color:var(--white);padding:100px 24px}
|
||
.hero h1{font-size:42px;font-weight:800;line-height:1.1;margin-bottom:20px}
|
||
.hero p{font-size:18px;color:#9aa3b2;max-width:900px;margin-bottom:32px}
|
||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px;margin-top:32px}
|
||
.stat-card{background:rgba(255,255,255,0.1);border-radius:12px;padding:20px;text-align:center}
|
||
.stat-card .num{font-size:42px;font-weight:800;color:var(--cyan);display:block}
|
||
.stat-card .label{color:#9aa3b2;font-size:13px;margin-top:8px;display:block;text-transform:uppercase;letter-spacing:1px}
|
||
.section{padding:80px 24px}
|
||
.section h2{font-size:32px;font-weight:700;margin-bottom:12px}
|
||
.section .subtitle{color:var(--gray-500);font-size:17px;margin-bottom:32px}
|
||
.controls{display:flex;gap:16px;margin-bottom:24px;flex-wrap:wrap}
|
||
.controls input{padding:12px 16px;border:2px solid var(--gray-100);border-radius:8px;font-size:16px;min-width:250px}
|
||
.controls input:focus{outline:none;border-color:var(--cyan)}
|
||
.controls select{padding:12px 16px;border:2px solid var(--gray-100);border-radius:8px;font-size:16px;background:var(--white)}
|
||
.table-container{overflow-x:auto;background:var(--gray-100);border-radius:16px;padding:24px}
|
||
table{width:100%;border-collapse:collapse;font-size:14px}
|
||
th{text-align:left;background:var(--ink);color:var(--white);padding:14px 12px;font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:0.5px;position:sticky;top:0}
|
||
td{padding:14px 12px;border-bottom:1px solid #e0e4e8;vertical-align:top}
|
||
tr:hover{background:#fff}
|
||
.company-col{font-weight:700;min-width:200px;font-size:15px}
|
||
.phone-col{font-family:monospace;color:var(--gray-500);white-space:nowrap}
|
||
.count-col{font-weight:800;font-size:18px;text-align:center}
|
||
.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700}
|
||
.badge-critical{background:var(--red);color:#fff}
|
||
.badge-high{background:var(--orange);color:#000}
|
||
.badge-medium{background:var(--cyan);color:var(--ink)}
|
||
.history-cell{font-size:13px}
|
||
.history-item{background:var(--white);border-left:3px solid var(--cyan);padding:8px 10px;margin-bottom:6px;border-radius:0 6px 6px 0}
|
||
.history-item:last-child{margin-bottom:0}
|
||
.history-date{font-weight:700;font-size:13px;margin-bottom:4px}
|
||
.history-details{display:flex;gap:12px;color:var(--gray-500);font-size:12px}
|
||
.history-details span{background:var(--gray-100);padding:3px 8px;border-radius:4px}
|
||
.days-badge{background:var(--gray-100);padding:4px 10px;border-radius:4px;font-size:12px;font-weight:700;display:inline-block}
|
||
.expand-btn{background:var(--cyan);border:none;color:var(--ink);padding:6px 14px;border-radius:6px;cursor:pointer;font-weight:700;font-size:12px}
|
||
.expand-btn:hover{background:#1be5ff}
|
||
.hidden-row{display:none}
|
||
@media (max-width:1024px){
|
||
.hero h1{font-size:32px}
|
||
.section h2{font-size:26px}
|
||
.container{padding:48px 20px}
|
||
table{font-size:13px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<section class="hero">
|
||
<div class="container" style="padding:40px 0">
|
||
<h1>📋 История всех обращений клиентов Алтел</h1>
|
||
<p>Полная информация по клиентам с повторными обращениями: сколько раз обращался, все номера заявок и каналов, даты каждого обращения</p>
|
||
<div class="stats">
|
||
<div class="stat-card">
|
||
<span class="num" id="totalClients">0</span>
|
||
<span class="label">Клиентов с повторами</span>
|
||
</div>
|
||
<div class="stat-card">
|
||
<span class="num" id="maxRequests">0</span>
|
||
<span class="label">Макс. обращений</span>
|
||
</div>
|
||
<div class="stat-card">
|
||
<span class="num" id="criticalClients">0</span>
|
||
<span class="label">Критично (5+ раз)</span>
|
||
</div>
|
||
<div class="stat-card">
|
||
<span class="num" id="totalRequests">0</span>
|
||
<span class="label">Всего заявок</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section">
|
||
<div class="container">
|
||
<h2>🔍 Все клиенты с повторными обращениями</h2>
|
||
<p class="subtitle">Отсортировано по количеству обращений (сначала самые частые)</p>
|
||
|
||
<div class="controls">
|
||
<input type="text" id="searchInput" placeholder="🔍 Поиск по компании или телефону...">
|
||
<select id="filterSelect">
|
||
<option value="all">Все обращения</option>
|
||
<option value="5+">5+ обращений</option>
|
||
<option value="3-4">3-4 обращения</option>
|
||
<option value="2">2 обращения</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th style="width:60px">#</th>
|
||
<th>Компания</th>
|
||
<th>Телефон</th>
|
||
<th style="text-align:center">Раз обращался</th>
|
||
<th>Период</th>
|
||
<th>История заявок (№ заявки / № канала / Дата)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<script>
|
||
let data = null;
|
||
|
||
async function loadData() {
|
||
const response = await fetch('data.json');
|
||
data = await response.json();
|
||
|
||
document.getElementById('totalClients').textContent = data.total_clients_with_repeats.toLocaleString('ru-RU');
|
||
document.getElementById('maxRequests').textContent = Math.max(...data.clients.map(c => c.total_requests)).toLocaleString('ru-RU');
|
||
document.getElementById('criticalClients').textContent = data.clients.filter(c => c.total_requests >= 5).length.toLocaleString('ru-RU');
|
||
document.getElementById('totalRequests').textContent = data.clients.reduce((sum, c) => sum + c.total_requests, 0).toLocaleString('ru-RU');
|
||
|
||
renderTable(data.clients);
|
||
}
|
||
|
||
function getBadge(count) {
|
||
if (count >= 5) return '<span class="badge badge-critical">Критично</span>';
|
||
if (count >= 3) return '<span class="badge badge-high">Высокий</span>';
|
||
return '<span class="badge badge-medium">Средний</span>';
|
||
}
|
||
|
||
function formatPhone(phone) {
|
||
const p = phone.toString();
|
||
return `+7 (${p.slice(0,3)}) ${p.slice(3,6)}-${p.slice(6,8)}-${p.slice(8)}`;
|
||
}
|
||
|
||
function renderTable(clients) {
|
||
const tbody = document.getElementById('tableBody');
|
||
tbody.innerHTML = '';
|
||
|
||
clients.forEach((client, index) => {
|
||
const tr = document.createElement('tr');
|
||
|
||
const historyHtml = client.requests.map((req, i) => `
|
||
<div class="history-item">
|
||
<div class="history-date">#${i + 1}. Заявка № ${req.id} • ${req.date}</div>
|
||
<div class="history-details">
|
||
<span>Канал: ${req.channel}</span>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
tr.innerHTML = `
|
||
<td style="text-align:center;font-weight:700;color:var(--gray-500)">${index + 1}</td>
|
||
<td class="company-col">${client.company}</td>
|
||
<td class="phone-col">${formatPhone(client.phone)}</td>
|
||
<td class="count-col">${getBadge(client.total_requests)}<div style="margin-top:6px;font-size:24px">${client.total_requests}</div></td>
|
||
<td>
|
||
<div style="font-weight:600">${client.first_date}</div>
|
||
<div style="color:var(--gray-500)">↓</div>
|
||
<div style="font-weight:600">${client.last_date}</div>
|
||
<div class="days-badge" style="margin-top:6px">${client.days_span} дн.</div>
|
||
</td>
|
||
<td class="history-cell">${historyHtml}</td>
|
||
`;
|
||
|
||
tbody.appendChild(tr);
|
||
});
|
||
}
|
||
|
||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||
const term = e.target.value.toLowerCase();
|
||
const filtered = data.clients.filter(c =>
|
||
c.company.toLowerCase().includes(term) ||
|
||
c.phone.toString().includes(term)
|
||
);
|
||
renderTable(filtered);
|
||
});
|
||
|
||
document.getElementById('filterSelect').addEventListener('change', (e) => {
|
||
const value = e.target.value;
|
||
let filtered = data.clients;
|
||
|
||
if (value === '5+') {
|
||
filtered = data.clients.filter(c => c.total_requests >= 5);
|
||
} else if (value === '3-4') {
|
||
filtered = data.clients.filter(c => c.total_requests >= 3 && c.total_requests < 5);
|
||
} else if (value === '2') {
|
||
filtered = data.clients.filter(c => c.total_requests === 2);
|
||
}
|
||
|
||
renderTable(filtered);
|
||
});
|
||
|
||
loadData();
|
||
</script>
|
||
</body>
|
||
</html>
|