v2: полная история всех обращений
This commit is contained in:
parent
66e33aacce
commit
02d9cf87b4
222
index.html
222
index.html
@ -3,80 +3,107 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Алтел — Повторные обращения клиентов</title>
|
<title>Алтел — Все повторные обращения клиентов</title>
|
||||||
<style>
|
<style>
|
||||||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--red:#FF4757;--orange:#FFA502}
|
: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}
|
*{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)}
|
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
|
||||||
.container{max-width:1200px;margin:0 auto;padding:80px 24px}
|
.container{max-width:1400px;margin:0 auto;padding:80px 24px}
|
||||||
.hero{background:var(--ink);color:var(--white);padding:100px 24px}
|
.hero{background:var(--ink);color:var(--white);padding:100px 24px}
|
||||||
.hero h1{font-size:48px;font-weight:800;line-height:1.1;margin-bottom:20px}
|
.hero h1{font-size:42px;font-weight:800;line-height:1.1;margin-bottom:20px}
|
||||||
.hero p{font-size:20px;color:#9aa3b2;max-width:800px}
|
.hero p{font-size:18px;color:#9aa3b2;max-width:900px;margin-bottom:32px}
|
||||||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:24px;margin-top:40px}
|
.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:24px;text-align:center}
|
.stat-card{background:rgba(255,255,255,0.1);border-radius:12px;padding:20px;text-align:center}
|
||||||
.stat-card .num{font-size:48px;font-weight:800;color:var(--cyan);display:block}
|
.stat-card .num{font-size:42px;font-weight:800;color:var(--cyan);display:block}
|
||||||
.stat-card .label{color:#9aa3b2;font-size:14px;margin-top:8px;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{padding:80px 24px}
|
||||||
.section h2{font-size:36px;font-weight:700;margin-bottom:16px}
|
.section h2{font-size:32px;font-weight:700;margin-bottom:12px}
|
||||||
.section .subtitle{color:var(--gray-500;font-size:18px;margin-bottom:32px}
|
.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-container{overflow-x:auto;background:var(--gray-100);border-radius:16px;padding:24px}
|
||||||
table{width:100%;border-collapse:collapse;font-size:15px}
|
table{width:100%;border-collapse:collapse;font-size:14px}
|
||||||
th{text-align:left;background:var(--ink);color:var(--white);padding:16px;font-weight:700;font-size:14px;text-transform:uppercase;letter-spacing:0.5px}
|
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:16px;border-bottom:1px solid #e0e4e8}
|
td{padding:14px 12px;border-bottom:1px solid #e0e4e8;vertical-align:top}
|
||||||
tr:hover{background:#fff}
|
tr:hover{background:#fff}
|
||||||
.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:13px;font-weight:700}
|
.company-col{font-weight:700;min-width:200px;font-size:15px}
|
||||||
.badge-warning{background:var(--orange);color:#000}
|
.phone-col{font-family:monospace;color:var(--gray-500);white-space:nowrap}
|
||||||
.badge-danger{background:var(--red);color:#fff}
|
.count-col{font-weight:800;font-size:18px;text-align:center}
|
||||||
.company-col{font-weight:600;min-width:250px}
|
.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700}
|
||||||
.phone-col{color:var(--gray-500);font-family:monospace}
|
.badge-critical{background:var(--red);color:#fff}
|
||||||
.id-col{font-family:monospace;color:var(--gray-500)}
|
.badge-high{background:var(--orange);color:#000}
|
||||||
.days-col{font-weight:700;color:var(--ink)}
|
.badge-medium{background:var(--cyan);color:var(--ink)}
|
||||||
.highlight{background:var(--cyan-50)}
|
.history-cell{font-size:13px}
|
||||||
@media (max-width:768px){
|
.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}
|
.hero h1{font-size:32px}
|
||||||
.section h2{font-size:28px}
|
.section h2{font-size:26px}
|
||||||
.stats{grid-template-columns:1fr}
|
|
||||||
.container{padding:48px 20px}
|
.container{padding:48px 20px}
|
||||||
th,td{padding:12px 8px;font-size:13px}
|
table{font-size:13px}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="container" style="padding:40px 0">
|
<div class="container" style="padding:40px 0">
|
||||||
<h1>📊 Анализ повторных обращений клиентов Алтел</h1>
|
<h1>📋 История всех обращений клиентов Алтел</h1>
|
||||||
<p>Мониторинг клиентов, которые обращались повторно после истечения 3-месячного срока устранения проблем. Проблема не была решена в установленный срок.</p>
|
<p>Полная информация по клиентам с повторными обращениями: сколько раз обращался, все номера заявок и каналов, даты каждого обращения</p>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span class="num" id="totalApps">0</span>
|
<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>
|
<span class="label">Всего заявок</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card">
|
|
||||||
<span class="num" id="uniqueClients">0</span>
|
|
||||||
<span class="label">Уникальных клиентов</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<span class="num" id="repeatCount" style="color:var(--red)">0</span>
|
|
||||||
<span class="label">Повторных (>3 мес)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>🔴 Клиенты с повторными обращениями после 3 месяцев</h2>
|
<h2>🔍 Все клиенты с повторными обращениями</h2>
|
||||||
<p class="subtitle">Период между заявками превышает 90 дней — проблема осталась нерешённой</p>
|
<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">
|
<div class="table-container">
|
||||||
<table id="repeatsTable">
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th style="width:60px">#</th>
|
||||||
<th>Компания</th>
|
<th>Компания</th>
|
||||||
<th>Телефон</th>
|
<th>Телефон</th>
|
||||||
<th>Первая заявка</th>
|
<th style="text-align:center">Раз обращался</th>
|
||||||
<th>Повторная заявка</th>
|
<th>Период</th>
|
||||||
<th>Дней между</th>
|
<th>История заявок (№ заявки / № канала / Дата)</th>
|
||||||
<th>Статус</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableBody"></tbody>
|
<tbody id="tableBody"></tbody>
|
||||||
@ -86,37 +113,90 @@ tr:hover{background:#fff}
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const data = {"total_applications":387,"unique_clients":128,"total_repeats":8,"repeats":[{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7081121715,"first_id":549607,"first_date":"05.01.2025","repeat_id":568952,"repeat_date":"24.06.2026","days_between":171},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7002242610,"first_id":561827,"first_date":"03.12.2025","repeat_id":568131,"repeat_date":"04.06.2026","days_between":183},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7002294367,"first_id":563603,"first_date":"30.01.2026","repeat_id":570640,"repeat_date":"03.08.2026","days_between":185},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7081484578,"first_id":539438,"first_date":"02.05.2024","repeat_id":566477,"repeat_date":"16.04.2026","days_between":714},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7081655470,"first_id":561826,"first_date":"03.12.2025","repeat_id":568369,"repeat_date":"10.06.2026","days_between":189},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7081916257,"first_id":563614,"first_date":"30.01.2026","repeat_id":568951,"repeat_date":"24.06.2026","days_between":144},{"company":"АО \"КАЗТЕЛЕПОРТ\"","phone":7086768198,"first_id":562359,"first_date":"19.12.2025","repeat_id":568953,"repeat_date":"24.06.2026","days_between":187},{"company":"АО «BEREKE BANK» ДОЧЕРНИЙ БАНК LESHA BANK LLC (PUBLIC))","phone":7081784711,"first_id":557582,"first_date":"11.08.2025","repeat_id":566616,"repeat_date":"21.04.2026","days_between":252}]};
|
let data = null;
|
||||||
|
|
||||||
document.getElementById('totalApps').textContent = data.total_applications.toLocaleString('ru-RU');
|
async function loadData() {
|
||||||
document.getElementById('uniqueClients').textContent = data.unique_clients.toLocaleString('ru-RU');
|
const response = await fetch('data.json');
|
||||||
document.getElementById('repeatCount').textContent = data.total_repeats.toLocaleString('ru-RU');
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
const tbody = document.getElementById('tableBody');
|
function getBadge(count) {
|
||||||
data.repeats.forEach((row, index) => {
|
if (count >= 5) return '<span class="badge badge-critical">Критично</span>';
|
||||||
const tr = document.createElement('tr');
|
if (count >= 3) return '<span class="badge badge-high">Высокий</span>';
|
||||||
if (index % 2 === 0) tr.className = 'highlight';
|
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 = '';
|
||||||
|
|
||||||
const statusBadge = row.days_between > 180
|
clients.forEach((client, index) => {
|
||||||
? '<span class="badge badge-danger">Критично (>6 мес)</span>'
|
const tr = document.createElement('tr');
|
||||||
: '<span class="badge badge-warning">>3 месяцев</span>';
|
|
||||||
|
const historyHtml = client.requests.map((req, i) => `
|
||||||
tr.innerHTML = `
|
<div class="history-item">
|
||||||
<td class="company-col">${row.company}</td>
|
<div class="history-date">#${i + 1}. Заявка № ${req.id} • ${req.date}</div>
|
||||||
<td class="phone-col">+7 (${row.phone.toString().slice(0,3)}) ${row.phone.toString().slice(3,6)}-${row.phone.toString().slice(6,8)}-${row.phone.toString().slice(8)}</td>
|
<div class="history-details">
|
||||||
<td>
|
<span>Канал: ${req.channel}</span>
|
||||||
<div style="font-weight:600">${row.first_date}</div>
|
</div>
|
||||||
<div class="id-col">№ ${row.first_id}</div>
|
</div>
|
||||||
</td>
|
`).join('');
|
||||||
<td>
|
|
||||||
<div style="font-weight:600">${row.repeat_date}</div>
|
tr.innerHTML = `
|
||||||
<div class="id-col">№ ${row.repeat_id}</div>
|
<td style="text-align:center;font-weight:700;color:var(--gray-500)">${index + 1}</td>
|
||||||
</td>
|
<td class="company-col">${client.company}</td>
|
||||||
<td class="days-col">${row.days_between} дн.</td>
|
<td class="phone-col">${formatPhone(client.phone)}</td>
|
||||||
<td>${statusBadge}</td>
|
<td class="count-col">${getBadge(client.total_requests)}<div style="margin-top:6px;font-size:24px">${client.total_requests}</div></td>
|
||||||
`;
|
<td>
|
||||||
tbody.appendChild(tr);
|
<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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user