let workbook = null; let worksheet = null; let rawData = []; let fileName = ''; const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); const fileInfo = document.getElementById('file-info'); const file_name = document.getElementById('file-name'); const fileMeta = document.getElementById('file-meta'); const analyzeBtn = document.getElementById('analyze-btn'); const errorMessage = document.getElementById('error-message'); const loading = document.getElementById('loading'); const results = document.getElementById('results'); uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); fileInput.click(); } }); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { handleFile(files[0]); } }); fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleFile(e.target.files[0]); } }); function handleFile(file) { const validTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', 'text/csv', 'application/vnd.ms-excel.sheet.macroEnabled.12' ]; const ext = file.name.split('.').pop().toLowerCase(); const isValid = validTypes.includes(file.type) || ['xlsx', 'xls', 'csv'].includes(ext); if (!isValid) { showError('Неподдерживаемый формат файла. Загрузите .xlsx, .xls или .csv'); return; } fileName = file.name; file_name.textContent = '📄 ' + file.name; fileMeta.textContent = 'Размер: ' + formatFileSize(file.size); fileInfo.classList.add('visible'); uploadArea.classList.add('has-file'); analyzeBtn.disabled = false; hideError(); results.classList.remove('visible'); const reader = new FileReader(); reader.onload = (e) => { try { const data = new Uint8Array(e.target.result); workbook = XLSX.read(data, { type: 'array' }); if (workbook.SheetNames.length > 0) { worksheet = workbook.Sheets[workbook.SheetNames[0]]; rawData = XLSX.utils.sheet_to_json(worksheet, { defval: '' }); if (rawData.length === 0) { showError('Файл пуст или не содержит данных'); analyzeBtn.disabled = true; return; } analyzeBtn.disabled = false; } } catch (err) { showError('Ошибка чтения файла: ' + err.message); analyzeBtn.disabled = true; } }; reader.onerror = () => { showError('Ошибка при чтении файла'); analyzeBtn.disabled = true; }; reader.readAsArrayBuffer(file); } analyzeBtn.addEventListener('click', () => { if (rawData.length === 0) { showError('Нет данных для анализа'); return; } loading.classList.add('visible'); results.classList.remove('visible'); setTimeout(() => { try { performAnalysis(); loading.classList.remove('visible'); results.classList.add('visible'); results.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (err) { loading.classList.remove('visible'); showError('Ошибка анализа: ' + err.message); } }, 500); }); function performAnalysis() { const columns = Object.keys(rawData[0]); const stats = calculateStats(rawData, columns); const insights = generateInsights(rawData, columns, stats); renderStats(stats); renderInsights(insights); renderCharts(rawData, columns, stats); renderTable(rawData, columns); } function calculateStats(data, columns) { const stats = { totalRows: data.length, totalColumns: columns.length, columns: columns, emptyCells: 0, numericColumns: [], textColumns: [], dateColumns: [], columnStats: {} }; columns.forEach(col => { const values = data.map(row => row[col]).filter(v => v !== '' && v !== null && v !== undefined); const emptyCount = data.length - values.length; stats.emptyCells += emptyCount; const firstValue = values[0]; const isNumeric = values.every(v => !isNaN(parseFloat(v)) && isFinite(v)); const isDate = values.some(v => { const d = new Date(v); return !isNaN(d.getTime()); }); if (isNumeric) { stats.numericColumns.push(col); const numValues = values.map(v => parseFloat(v)); stats.columnStats[col] = { type: 'numeric', min: Math.min(...numValues), max: Math.max(...numValues), avg: numValues.reduce((a, b) => a + b, 0) / numValues.length, sum: numValues.reduce((a, b) => a + b, 0), empty: emptyCount }; } else if (isDate) { stats.dateColumns.push(col); stats.columnStats[col] = { type: 'date', empty: emptyCount }; } else { stats.textColumns.push(col); const unique = [...new Set(values)]; const counts = {}; values.forEach(v => { counts[v] = (counts[v] || 0) + 1; }); const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]); stats.columnStats[col] = { type: 'text', unique: unique.length, top: sorted.slice(0, 5), empty: emptyCount }; } }); return stats; } function generateInsights(data, columns, stats) { const insights = []; insights.push({ icon: '📊', text: `Всего записей: ${stats.totalRows} в ${stats.totalColumns} колонках` }); if (stats.numericColumns.length > 0) { insights.push({ icon: '💰', text: `Числовые колонки: ${stats.numericColumns.join(', ')}` }); stats.numericColumns.forEach(col => { const s = stats.columnStats[col]; if (s && s.type === 'numeric') { insights.push({ icon: '📈', text: `${col}: среднее = ${formatNumber(s.avg)}, мин = ${formatNumber(s.min)}, макс = ${formatNumber(s.max)}` }); } }); } if (stats.textColumns.length > 0) { insights.push({ icon: '🏷️', text: `Текстовые колонки: ${stats.textColumns.join(', ')}` }); stats.textColumns.forEach(col => { const s = stats.columnStats[col]; if (s && s.type === 'text' && s.top.length > 0) { const topList = s.top.map(item => item[0]).slice(0, 3).join(', '); insights.push({ icon: '🔝', text: `${col}: чаще всего встречается "${topList}"` }); } }); } const emptyPercent = ((stats.emptyCells / (stats.totalRows * stats.totalColumns)) * 100).toFixed(1); if (emptyPercent > 10) { insights.push({ icon: '⚠️', text: `Внимание: ${emptyPercent}% пустых ячеек — проверьте полноту данных` }); } return insights; } function renderStats(stats) { const grid = document.getElementById('stats-grid'); grid.innerHTML = `