// Map view — главный экран const MapView = { map: null, markers: [], placeMarkers: [], // маркеры баров/ресторанов placeMarkersLayer: null, radius: 5, visible: true, nearby: [], places: [], showPlaces: true, async render(root) { root.innerHTML = `

🗺️ Карта

Я на карте
Другие пользователи видят твоё местоположение ±300м
Нажми чтобы получить координаты
`; if (!this.map) { this.map = L.map('map').setView([55.7558, 37.6173], 12); // 2GIS tile layer L.tileLayer('https://tile2.maps.2gis.com/tiles?x={x}&y={y}&z={z}', { attribution: '© 2GIS', maxZoom: 19, }).addTo(this.map); } setTimeout(() => this.map.invalidateSize(), 100); document.getElementById('visible-toggle').addEventListener('change', async (e) => { this.visible = e.target.checked; try { await API.Auth.setVisibility(this.visible); if (window._lastKnownPos) this.applyPosition(window._lastKnownPos); } catch (err) { console.warn(err); TG_APP.showAlert('Не удалось обновить видимость'); } }); document.getElementById('radius-sel').addEventListener('change', async (e) => { this.radius = parseInt(e.target.value, 10); document.getElementById('radius-label') && (document.getElementById('radius-label').textContent = this.radius + ' км'); if (window._lastKnownPos) { await this.refreshNearbyFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude); await this.refreshPlacesFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude); } }); document.getElementById('loc-share-btn').onclick = (e) => { e.preventDefault(); this.requestLocation(); }; document.getElementById('places-toggle').onclick = () => { this.showPlaces = !this.showPlaces; const txt = document.getElementById('places-toggle-text'); if (txt) txt.textContent = this.showPlaces ? 'Скрыть бары' : 'Показать бары'; if (this.placeMarkersLayer) { if (this.showPlaces) this.map.addLayer(this.placeMarkersLayer); else this.map.removeLayer(this.placeMarkersLayer); } const list = document.getElementById('places-list'); if (list) list.style.display = this.showPlaces ? 'block' : 'none'; }; // Если уже есть сохранённые координаты — покажем на карте if (window._lastKnownPos) { this.applyPosition(window._lastKnownPos); } else { this.requestLocation(); } }, requestLocation() { if (!navigator.geolocation) { const s = document.getElementById('loc-status'); if (s) s.textContent = 'Геолокация недоступна в браузере'; return; } const btn = document.getElementById('loc-share-btn'); const status = document.getElementById('loc-status'); if (btn) { btn.disabled = true; btn.textContent = '⏳ Запрашиваю…'; } if (status) status.textContent = 'Браузер спросит разрешение…'; navigator.geolocation.getCurrentPosition( async (pos) => { window._lastKnownPos = pos; if (btn) { btn.disabled = false; btn.innerHTML = '📍 Обновить геолокацию'; } if (status) status.textContent = `✓ ${pos.coords.latitude.toFixed(4)}, ${pos.coords.longitude.toFixed(4)}`; await this.applyPosition(pos); }, (err) => { if (btn) { btn.disabled = false; btn.innerHTML = '📍 Попробовать снова'; } if (status) { const msg = this.geolocationErrorText(err); status.innerHTML = '⚠️ ' + msg + (err.code === 1 ? '
Открой Настройки → Сайты → Геолокация → buhapp.mygoodservice.ru' : ''); } console.warn('geolocation error:', err); }, { enableHighAccuracy: true, timeout: 15000, maximumAge: 60000 } ); }, geolocationErrorText(err) { switch (err.code) { case err.PERMISSION_DENIED: return 'Доступ к геолокации запрещён.'; case err.POSITION_UNAVAILABLE: return 'Не удалось определить координаты (нет GPS/Wi-Fi).'; case err.TIMEOUT: return 'Таймаут запроса геолокации.'; default: return 'Неизвестная ошибка геолокации.'; } }, async applyPosition(pos) { const lat = pos.coords.latitude; const lng = pos.coords.longitude; try { await API.Auth.updateLocation(lat, lng, this.visible); } catch (e) { console.warn('updateLocation:', e); } if (this.map) { this.map.setView([lat, lng], 13); this.map.eachLayer((layer) => { if (layer instanceof L.Circle) this.map.removeLayer(layer); }); L.circle([lat, lng], { radius: this.radius * 1000, color: '#FF6B35', fillColor: '#FF6B35', fillOpacity: 0.1, weight: 2, }).addTo(this.map); this.refreshNearbyFromPos(lat, lng); this.refreshPlacesFromPos(lat, lng); } }, async refreshNearby() { if (!navigator.geolocation) return; navigator.geolocation.getCurrentPosition(async (pos) => { this.refreshNearbyFromPos(pos.coords.latitude, pos.coords.longitude); }); }, async refreshNearbyFromPos(lat, lng) { try { const r = await API.Auth.searchNearby(lat, lng, this.radius); this.nearby = r.results || []; const c = document.getElementById('nearby-count'); if (c) c.textContent = '👥 ' + this.nearby.length + ' чел. рядом'; this.markers.forEach(m => this.map.removeLayer(m)); this.markers = []; this.nearby.forEach((n) => { const icon = L.divIcon({ className: 'custom-marker', html: `
${escapeHtml((n.name || '?')[0])}
`, iconSize: [36, 36], }); const m = L.marker([n.lat, n.lng], { icon }).addTo(this.map); m.bindTooltip(n.name + ' • ' + (n.distance_m / 1000).toFixed(1) + ' км', { direction: 'top' }); m.on('click', () => App.openUserProfile(n.user_id, n.name)); this.markers.push(m); }); } catch (e) { const c = document.getElementById('nearby-count'); if (c) c.textContent = '📍 нужна геолокация'; console.warn(e); } }, async refreshPlacesFromPos(lat, lng) { try { const r = await API.PlacesApi.nearby(lat, lng, this.radius, 50); this.places = r.places || []; // маркеры if (this.placeMarkersLayer) this.map.removeLayer(this.placeMarkersLayer); this.placeMarkersLayer = L.layerGroup(); this.places.forEach((p) => { const icon = L.divIcon({ className: 'place-marker', html: `
${placeEmoji(p.type)}
`, iconSize: [32, 32], }); const m = L.marker([p.lat, p.lng], { icon }); m.bindTooltip( `${escapeHtml(p.name)}
${placeTypeName(p.type)} • ${(p.distance_m/1000).toFixed(1)} км${p.cuisine ? '
🍴 ' + escapeHtml(p.cuisine) : ''}`, { direction: 'top' } ); m.on('click', () => this.openPlaceCard(p)); this.placeMarkersLayer.addLayer(m); }); if (this.showPlaces) this.placeMarkersLayer.addTo(this.map); // список снизу this.renderPlacesList(); } catch (e) { console.warn('refreshPlaces:', e); } }, renderPlacesList() { const box = document.getElementById('places-list'); if (!box) return; if (!this.places.length) { box.innerHTML = '
Бары рядом не найдены. Попробуй увеличить радиус.
'; return; } box.innerHTML = '
🍻 Где выпить / поесть:
' + this.places.map((p) => `
${placeEmoji(p.type)}
${escapeHtml(p.name)}
${placeTypeName(p.type)} • ${(p.distance_m/1000).toFixed(1)} км${p.cuisine ? ' • ' + escapeHtml(p.cuisine) : ''}
`).join(''); // клики по карточкам box.querySelectorAll('.card.clickable').forEach((el) => { el.onclick = () => { const id = parseInt(el.dataset.placeId, 10); const p = this.places.find(x => x.osm_id === id); if (p) this.openPlaceCard(p); }; }); box.querySelectorAll('[data-invite-place]').forEach((btn) => { btn.onclick = (e) => { e.stopPropagation(); const id = parseInt(btn.dataset.invitePlace, 10); const p = this.places.find(x => x.osm_id === id); if (p) this.inviteToPlace(p); }; }); }, openPlaceCard(p) { const msg = `${p.name} (${placeTypeName(p.type)})\n📍 ${p.lat.toFixed(4)}, ${p.lng.toFixed(4)}${p.cuisine ? '\n🍴 ' + p.cuisine : ''}${p.hours ? '\n🕐 ' + p.hours : ''}${p.phone ? '\n📞 ' + p.phone : ''}`; TG_APP.showAlert(msg); }, async inviteToPlace(p) { // открываем список чатов для выбора кому предложить встречу const chats = await API.ChatApi.listChats(); if (!chats.chats || !chats.chats.length) { TG_APP.showAlert('Сначала начни чат с кем-нибудь'); return; } // простая реализация — показать список через confirm const list = chats.chats.slice(0, 5).map((c, i) => `${i+1}. ${c.other_name || c.otherName || 'собеседник'}`).join('\n'); TG_APP.showConfirm( `Отправить предложение встретиться в "${p.name}"?\n\nТвои чаты:\n${list}\n\nПредложение будет отправлено в самый новый чат.` ).then(async (ok) => { if (!ok) return; const target = chats.chats[0]; const text = `🍻 Давай встретимся здесь: ${p.name}\n📍 https://2gis.ru/?m=${p.lng.toFixed(6)}%2C${p.lat.toFixed(6)}%2F16\n${p.address ? '🏠 ' + p.address : ''}`; try { await API.ChatApi.sendMessage(target.chat.id || target.chat.id, text); TG_APP.showAlert('Приглашение отправлено в чат с ' + (target.other_name || 'собеседником')); } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } }); }, }; function placeEmoji(type) { switch (type) { case 'bar': case 'pub': case 'biergarten': return '🍺'; case 'restaurant': return '🍽️'; case 'cafe': return '☕'; case 'nightclub': return '🎉'; default: return '🍴'; } } function placeTypeName(type) { switch (type) { case 'bar': return 'Бар'; case 'pub': return 'Паб'; case 'biergarten': return 'Пивная'; case 'restaurant': return 'Ресторан'; case 'cafe': return 'Кафе'; case 'nightclub': return 'Ночной клуб'; default: return 'Заведение'; } } window.MapView = MapView;