altel-compare/index.html
2026-08-25 12:15:12 +00:00

325 lines
18 KiB
HTML
Raw Permalink 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">
<title>Сравнение заявок Altel - Июль vs Август</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAADL0lEQVR4nO2cTUhUURTHf+OoaWpqH4iZRBRBEEHQpi3atGkRQdtWtYqgRYu2LYKgTYu2bUFRmzZtIygiCKJFEEGEUdtWtYqgRYu2LYKgTYu2bUFRmzZtIygiCPq3aRtBETXvzcy9c+8M/Q/cYebe8/7n3HPPPfc+4L8y/gWmApkCmAKYApiJAJqbmwWwAngMvAReAi+Al8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAReAi+Bl8BL4CXwEngJvAR/BP8Bv5YzJ0H5jQAAAABJRU5ErkJggg==">
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--success:#10B981;--warning:#F59E0B;--danger:#EF4444}
*{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:1200px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white);padding:60px 24px}
.hero h1{font-size:42px;font-weight:800;line-height:1.1;margin-bottom:16px}
.hero p{font-size:18px;color:#9aa3b2;max-width:700px}
.section{padding:60px 24px}
.section h2{font-size:32px;font-weight:700;margin-bottom:24px}
.card{background:var(--gray-100);border-radius:12px;padding:24px;margin-bottom:20px}
.upload-zone{border:2px dashed var(--gray-500);border-radius:12px;padding:40px;text-align:center;background:var(--white);cursor:pointer;transition:all 0.2s}
.upload-zone:hover{border-color:var(--cyan);background:var(--cyan-50)}
.upload-zone.dragover{border-color:var(--cyan);background:var(--cyan-50)}
.upload-icon{font-size:48px;margin-bottom:16px}
.file-input{display:none}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer;font-size:17px}
.btn:hover{background:#1be5ff}
.btn:disabled{background:var(--gray-500);cursor:not-allowed}
.file-status{margin-top:16px;padding:12px;border-radius:8px;background:var(--cyan-50);display:none}
.file-status.show{display:block}
.file-status.error{background:#FEF2F2;color:var(--danger)}
.file-status.success{background:#ECFDF5;color:var(--success)}
.results{display:none}
.results.show{display:block}
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:32px}
.stat-card{background:var(--white);border-radius:12px;padding:20px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,0.1)}
.stat-value{font-size:36px;font-weight:800;color:var(--ink);margin-bottom:8px}
.stat-label{font-size:14px;color:var(--gray-500)}
.table-container{overflow-x:auto;margin-bottom:20px}
table{width:100%;border-collapse:collapse;background:var(--white);border-radius:8px;overflow:hidden}
th,td{padding:12px 16px;text-align:left;border-bottom:1px solid var(--gray-100)}
th{background:var(--ink);color:var(--white);font-weight:600;font-size:14px}
tr:hover{background:var(--cyan-50)}
.match-highlight{background:#FEF3C7;font-weight:600}
.not-found{color:var(--danger)}
.actions{margin-top:24px;display:flex;gap:12px;flex-wrap:wrap}
.btn-secondary{background:transparent;color:var(--ink);border:2px solid var(--ink);padding:12px 24px;border-radius:8px;font-weight:700;cursor:pointer}
.btn-secondary:hover{background:var(--ink);color:var(--white)}
.progress-bar{height:4px;background:var(--gray-100);border-radius:2px;overflow:hidden;margin-top:16px;display:none}
.progress-bar.show{display:block}
.progress-fill{height:100%;background:var(--cyan);width:0;transition:width 0.3s}
@media(max-width:768px){.hero h1{font-size:28px}.section h2{font-size:24px}.container{padding:40px 16px}.stats-grid{grid-template-columns:1fr 1fr}}
</style>
</head>
<body>
<section class="hero">
<h1>📊 Сравнение заявок Altel</h1>
<p>Загрузите файлы за Июль и Август 2026 — система найдёт общие номера и покажет данные по дозвону</p>
</section>
<section class="section">
<div class="container">
<h2>1. Загрузка файлов</h2>
<div class="card">
<div class="upload-zone" id="julyZone">
<div class="upload-icon">📅</div>
<strong>Файл за Июль 2026</strong>
<p style="color:var(--gray-500);margin:8px 0 16px">Перетащите файл или нажмите для выбора</p>
<input type="file" class="file-input" id="julyFile" accept=".xlsx,.xls">
<button class="btn" onclick="document.getElementById('julyFile').click()">Выбрать файл</button>
<div class="file-status" id="julyStatus"></div>
</div>
</div>
<div class="card">
<div class="upload-zone" id="augustZone">
<div class="upload-icon">📅</div>
<strong>Файл за Август 2026</strong>
<p style="color:var(--gray-500);margin:8px 0 16px">Перетащите файл или нажмите для выбора</p>
<input type="file" class="file-input" id="augustFile" accept=".xlsx,.xls">
<button class="btn" onclick="document.getElementById('augustFile').click()">Выбрать файл</button>
<div class="file-status" id="augustStatus"></div>
</div>
</div>
<div class="progress-bar" id="progressBar"><div class="progress-fill" id="progressFill"></div></div>
<div class="actions" style="margin-top:24px">
<button class="btn" id="compareBtn" disabled onclick="compareFiles()">🔍 Сравнить файлы</button>
<button class="btn-secondary" onclick="resetAll()">🔄 Начать заново</button>
</div>
</div>
</section>
<section class="section results" id="resultsSection">
<div class="container">
<h2>2. Результаты сравнения</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="julyCount">0</div>
<div class="stat-label">Номеров в Июле</div>
</div>
<div class="stat-card">
<div class="stat-value" id="augustCount">0</div>
<div class="stat-label">Номеров в Августе</div>
</div>
<div class="stat-card">
<div class="stat-value" id="matchCount">0</div>
<div class="stat-label">Совпадений</div>
</div>
<div class="stat-card">
<div class="stat-value" id="newCount">0</div>
<div class="stat-label">Новых в Августе</div>
</div>
</div>
<div class="card">
<h3 style="margin-bottom:16px">📋 Таблица совпадений</h3>
<div class="table-container">
<table id="resultsTable">
<thead>
<tr>
<th>Номер телефона</th>
<th>Дозвон (Июль)</th>
<th>Статус (Август)</th>
</tr>
</thead>
<tbody id="resultsBody"></tbody>
</table>
</div>
<button class="btn" onclick="exportToCSV()" style="margin-top:16px">📥 Экспорт в CSV</button>
</div>
</div>
</section>
<script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>
<script>
let julyData = null;
let augustData = null;
let matchResults = [];
['july', 'august'].forEach(period => {
const zone = document.getElementById(`${period}Zone`);
const input = document.getElementById(`${period}File`);
zone.addEventListener('dragover', e => {
e.preventDefault();
zone.classList.add('dragover');
});
zone.addEventListener('dragleave', () => {
zone.classList.remove('dragover');
});
zone.addEventListener('drop', e => {
e.preventDefault();
zone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length) handleFile(files[0], period);
});
input.addEventListener('change', e => {
if (e.target.files.length) handleFile(e.target.files[0], period);
});
});
function handleFile(file, period) {
const statusEl = document.getElementById(`${period}Status`);
statusEl.className = 'file-status show';
statusEl.textContent = `⏳ Загрузка: ${file.name}`;
const reader = new FileReader();
reader.onload = e => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
const phoneColumn = findPhoneColumn(jsonData);
const dozvonColumn = findDozvonColumn(jsonData);
const extracted = [];
for (let i = 1; i < jsonData.length; i++) {
const row = jsonData[i];
if (row[phoneColumn]) {
extracted.push({
phone: normalizePhone(String(row[phoneColumn])),
dozvon: dozvonColumn !== -1 && row[dozvonColumn] ? String(row[dozvonColumn]) : 'Н/Д',
rawData: row
});
}
}
if (period === 'july') {
julyData = extracted;
document.getElementById('julyCount').textContent = extracted.length;
} else {
augustData = extracted;
document.getElementById('augustCount').textContent = extracted.length;
}
statusEl.className = 'file-status show success';
statusEl.textContent = `✅ ${file.name} (${extracted.length} номеров)`;
document.getElementById('compareBtn').disabled = !(julyData && augustData);
} catch (err) {
statusEl.className = 'file-status show error';
statusEl.textContent = `❌ Ошибка: ${err.message}`;
}
};
reader.readAsArrayBuffer(file);
}
function findPhoneColumn(data) {
const headers = data[0] || [];
for (let i = 0; i < headers.length; i++) {
const h = String(headers[i] || '').toLowerCase();
if (h.includes('номер') || h.includes('phone') || h.includes('тел')) return i;
}
return 0;
}
function findDozvonColumn(data) {
const headers = data[0] || [];
for (let i = 0; i < headers.length; i++) {
const h = String(headers[i] || '').toLowerCase();
if (h.includes('дозвон') || h.includes('результат') || h.includes('статус')) return i;
}
return -1;
}
function normalizePhone(phone) {
return phone.replace(/\D/g, '').replace(/^7/, '8') || phone;
}
function compareFiles() {
if (!julyData || !augustData) return;
const progressBar = document.getElementById('progressBar');
const progressFill = document.getElementById('progressFill');
progressBar.classList.add('show');
progressFill.style.width = '0%';
setTimeout(() => {
const augustMap = new Map();
augustData.forEach(item => {
augustMap.set(item.phone, item);
});
matchResults = [];
julyData.forEach(julyItem => {
const augustItem = augustMap.get(julyItem.phone);
if (augustItem) {
matchResults.push({
phone: julyItem.phone,
julyDozvon: julyItem.dozvon,
augustStatus: augustItem.dozvon,
isMatch: true
});
}
});
const augustPhones = new Set(augustData.map(i => i.phone));
const newCount = julyData.filter(i => !augustPhones.has(i.phone)).length;
document.getElementById('matchCount').textContent = matchResults.length;
document.getElementById('newCount').textContent = newCount;
const tbody = document.getElementById('resultsBody');
tbody.innerHTML = matchResults.slice(0, 100).map(r => `
<tr>
<td class="match-highlight">8${r.phone.slice(1)}</td>
<td>${r.julyDozvon}</td>
<td>${r.augustStatus}</td>
</tr>
`).join('');
if (matchResults.length > 100) {
tbody.innerHTML += `<tr><td colspan="3" style="text-align:center;color:var(--gray-500)">... ещё ${matchResults.length - 100} записей (экспортируйте в CSV для полного просмотра)</td></tr>`;
}
progressFill.style.width = '100%';
document.getElementById('resultsSection').classList.add('show');
document.getElementById('resultsSection').scrollIntoView({ behavior: 'smooth' });
}, 500);
}
function exportToCSV() {
if (!matchResults.length) return;
const csv = 'Номер телефона,Дозвон (Июль),Статус (Август)\n' +
matchResults.map(r => `8${r.phone.slice(1)},${r.julyDozvon},${r.augustStatus}`).join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'сравнение_июль_август.csv';
link.click();
}
function resetAll() {
julyData = null;
augustData = null;
matchResults = [];
document.getElementById('julyFile').value = '';
document.getElementById('augustFile').value = '';
document.getElementById('julyStatus').className = 'file-status';
document.getElementById('augustStatus').className = 'file-status';
document.getElementById('julyStatus').textContent = '';
document.getElementById('augustStatus').textContent = '';
document.getElementById('compareBtn').disabled = true;
document.getElementById('resultsSection').classList.remove('show');
document.getElementById('progressBar').classList.remove('show');
['julyCount', 'augustCount', 'matchCount', 'newCount'].forEach(id => {
document.getElementById(id).textContent = '0';
});
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
</body>
</html>