v2: полная история всех обращений

This commit is contained in:
Dauren777 2026-08-28 09:23:43 +00:00
parent 66e33aacce
commit 02d9cf87b4
2 changed files with 2906 additions and 120 deletions

2804
data.json

File diff suppressed because it is too large Load Diff

View File

@ -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} &bull; ${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>