/
baga228
/
project_PIAS
Обзор
Документация
Войти
/
baga228
/
project_PIAS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/script.js
361 строка
12 KB
baga228
upload files
26 дек 2025, 04:00
26 дек 2025, 04:00
1f57827
Код
Авторство
О чём код?
// ========================================== // АНАЛИЗАТОР ЦЕН НА НЕДВИЖИМОСТЬ РОСТОВА // Клиентская часть (Frontend) // ========================================== const API_BASE_URL = 'http://localhost:5000/api'; // ========================================== // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ // ========================================== let allDistricts = []; let allProperties = []; let selectedDistricts = []; // ========================================== // ИНИЦИАЛИЗАЦИЯ ПРИЛОЖЕНИЯ // ========================================== document.addEventListener('DOMContentLoaded', () => { console.log('✓ Приложение загружено'); loadDistricts(); loadProperties(); setupEventListeners(); }); // ========================================== // ЗАГРУЗКА ДАННЫХ С СЕРВЕРА // ========================================== async function loadDistricts() { try { const response = await fetch(`${API_BASE_URL}/districts`); allDistricts = await response.json(); renderDistrictFilter(); updateStats(); } catch (error) { console.error('Ошибка загрузки районов:', error); showError('Не удалось загрузить районы'); } } async function loadProperties() { try { const response = await fetch(`${API_BASE_URL}/properties`); allProperties = await response.json(); renderPropertiesTable(allProperties); } catch (error) { console.error('Ошибка загрузки объектов:', error); showError('Не удалось загрузить объекты недвижимости'); } } // ========================================== // РЕНДЕР ФИЛЬТРА РАЙОНОВ // ========================================== function renderDistrictFilter() { const container = document.getElementById('districtsFilter'); container.innerHTML = ''; allDistricts.forEach(district => { const label = document.createElement('label'); label.innerHTML = ` <input type="checkbox" value="${district.id}" class="district-checkbox"> ${district.name} `; label.querySelector('input').addEventListener('change', updateSelectedDistricts); container.appendChild(label); }); } function updateSelectedDistricts() { const checkboxes = document.querySelectorAll('.district-checkbox:checked'); selectedDistricts = Array.from(checkboxes).map(cb => parseInt(cb.value)); filterPropertiesByDistrict(); } // ========================================== // ФИЛЬТРАЦИЯ ОБЪЕКТОВ ПО РАЙОНАМ // ========================================== function filterPropertiesByDistrict() { if (selectedDistricts.length === 0) { renderPropertiesTable(allProperties); } else { const filtered = allProperties.filter(prop => selectedDistricts.includes(prop.district_id) ); renderPropertiesTable(filtered); } } // ========================================== // РЕНДЕР ТАБЛИЦЫ ОБЪЕКТОВ // ========================================== function renderPropertiesTable(properties) { const tbody = document.getElementById('propertiesList'); tbody.innerHTML = ''; if (properties.length === 0) { tbody.innerHTML = '<tr><td colspan="6" class="empty-state">Нет объектов</td></tr>'; return; } properties.forEach(property => { const pricePerSqm = Math.round(property.price / property.area); const row = document.createElement('tr'); row.classList.add('property-row'); row.innerHTML = ` <td>${property.address}</td> <td><strong>${property.district_name}</strong></td> <td>${property.area.toFixed(1)}</td> <td>${property.rooms || '-'}</td> <td class="price">${formatPrice(property.price)}</td> <td class="price-per-sqm">${formatPrice(pricePerSqm)}</td> `; // При клике по строке грузим данные из 2GIS row.addEventListener('click', () => { loadGeocode(property.address + ', Ростов-на-Дону'); }); tbody.appendChild(row); }); } // ========================================== // ЗАПРОС К 2GIS ЧЕРЕЗ БЭКЕНД // ========================================== async function loadGeocode(address) { const block = document.getElementById('geocodeResult'); const content = document.getElementById('geocodeContent'); if (!block || !content) return; block.style.display = 'block'; content.innerHTML = 'Загрузка данных из 2GIS...'; try { const url = `${API_BASE_URL.replace('/api', '')}/api/geocode?address=` + encodeURIComponent(address); const response = await fetch(url); const data = await response.json(); if (!data.success) { content.innerHTML = 'Не удалось получить данные из 2GIS: ' + (data.error || ''); return; } const item = data.raw || {}; const fullName = item.full_name || '—'; const id = item.id || '—'; const type = item.type || '—'; content.innerHTML = ` <p><strong>Адрес запроса:</strong> ${data.address}</p> <p><strong>Полное имя объекта (2GIS):</strong> ${fullName}</p> <p><strong>ID объекта 2GIS:</strong> ${id}</p> <p><strong>Тип объекта:</strong> ${type}</p> <p><strong>Координаты:</strong> широта = ${data.lat ?? '—'}, долгота = ${data.lon ?? '—'}</p> `; } catch (error) { console.error('Ошибка 2GIS:', error); content.innerHTML = 'Ошибка при обращении к 2GIS.'; } } // ========================================== // АНАЛИЗ ЦЕН ПО РАЙОНАМ // ========================================== async function analyzeDistricts() { try { const response = await fetch(`${API_BASE_URL}/analysis`); const analysis = await response.json(); renderAnalysis(analysis); renderChart(analysis); } catch (error) { console.error('Ошибка анализа:', error); showError('Не удалось провести анализ'); } } function renderAnalysis(analysis) { const container = document.getElementById('analysisContainer'); container.innerHTML = ''; if (analysis.length === 0) { container.innerHTML = '<p class="empty-state">Нет данных для анализа</p>'; return; } analysis.forEach(result => { const card = document.createElement('div'); card.className = 'analysis-card'; card.innerHTML = ` <h3>${result.district_name}</h3> <div class="metric"> <span class="metric-label">Объектов:</span> <span class="metric-value">${result.property_count}</span> </div> <div class="metric"> <span class="metric-label">Средняя цена:</span> <span class="metric-value">${formatPrice(result.avg_price)}</span> </div> <div class="metric"> <span class="metric-label">Цена за м²:</span> <span class="metric-value">${formatPrice(result.avg_price_per_sqm)}</span> </div> <div class="metric"> <span class="metric-label">Мин./Макс.:</span> <span class="metric-value">${formatPrice(result.min_price)} - ${formatPrice(result.max_price)}</span> </div> `; container.appendChild(card); }); } // ========================================== // ГРАФИК (CANVAS) // ========================================== let chartInstance = null; function renderChart(analysis) { // Берём топ-5 самых дорогих районов const top5 = analysis.slice(0, 5); const labels = top5.map(a => a.district_name); const prices = top5.map(a => a.avg_price); const colors = [ '#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6' ]; const canvas = document.getElementById('pricesChart'); const ctx = canvas.getContext('2d'); // Удаляем старый график если существует if (chartInstance) { chartInstance.destroy(); } chartInstance = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Средняя цена (₽)', data: prices, backgroundColor: colors, borderColor: colors, borderWidth: 2, borderRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: true, position: 'top' } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return formatPrice(value); } } } } } }); } // ========================================== // ПОИСК И ФИЛЬТРАЦИЯ // ========================================== document.getElementById('searchInput')?.addEventListener('input', (e) => { const query = e.target.value.toLowerCase(); const filtered = allProperties.filter(prop => prop.address.toLowerCase().includes(query) || prop.price.toString().includes(query) ); renderPropertiesTable(filtered); }); // ========================================== // ОБНОВЛЕНИЕ СТАТИСТИКИ // ========================================== function updateStats() { const statsBlock = document.getElementById('statsBlock'); if (allProperties.length === 0) { statsBlock.innerHTML = '<p>Загрузка статистики...</p>'; return; } const prices = allProperties.map(p => p.price); const avgPrice = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length); const minPrice = Math.min(...prices); const maxPrice = Math.max(...prices); statsBlock.innerHTML = ` <div class="stat-item"> <span class="stat-label">Объектов:</span> <span class="stat-value">${allProperties.length}</span> </div> <div class="stat-item"> <span class="stat-label">Средняя цена:</span> <span class="stat-value">${formatPrice(avgPrice)}</span> </div> <div class="stat-item"> <span class="stat-label">Минимум:</span> <span class="stat-value">${formatPrice(minPrice)}</span> </div> <div class="stat-item"> <span class="stat-label">Максимум:</span> <span class="stat-value">${formatPrice(maxPrice)}</span> </div> <div class="stat-item"> <span class="stat-label">Районов:</span> <span class="stat-value">${allDistricts.length}</span> </div> `; } // ========================================== // ОБРАБОТЧИКИ СОБЫТИЙ // ========================================== function setupEventListeners() { document.getElementById('btnAnalyze').addEventListener('click', analyzeDistricts); document.getElementById('btnRefresh').addEventListener('click', () => { location.reload(); }); } // ========================================== // ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ // ========================================== function formatPrice(price) { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0 }).format(price); } function showError(message) { alert('❌ Ошибка: ' + message); }