buhapp-telegram/public/js/views/map.js

172 lines
6.7 KiB
JavaScript
Raw 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.

// Map view — главный экран
const MapView = {
map: null,
markers: [],
radius: 5,
visible: true,
nearby: [],
async render(root) {
root.innerHTML = `
<div class="view">
<h1>🗺️ Карта</h1>
<div class="card" id="my-info">
<div class="row">
<div>
<strong>Я на карте</strong>
<div class="privacy-note">Другие пользователи видят твоё местоположение ±300м</div>
</div>
<label class="switch">
<input type="checkbox" id="visible-toggle" ${this.visible ? 'checked' : ''}>
<span class="slider"></span>
</label>
</div>
<button class="btn" id="loc-share-btn" style="margin-top:8px;">📍 Поделиться геолокацией</button>
<div class="privacy-note" id="loc-status">Нажми чтобы получить координаты</div>
</div>
<div id="map" style="height:400px;border-radius:12px;overflow:hidden;margin-bottom:12px;"></div>
<div class="row">
<label>Радиус: <strong id="radius-label">${this.radius} км</strong></label>
<select id="radius-sel" style="background:var(--bg-elev);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:6px;">
<option value="1">1 км</option>
<option value="5" selected>5 км</option>
<option value="10">10 км</option>
<option value="25">25 км</option>
<option value="50">50 км</option>
</select>
</div>
<div id="nearby-count" class="sub">—</div>
</div>
`;
if (!this.map) {
this.map = L.map('map').setView([55.7558, 37.6173], 12);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
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);
this.refreshLocation();
} catch (err) {
API.Store && console.warn(err);
TG_APP.showAlert('Не удалось обновить видимость');
}
});
document.getElementById('radius-sel').addEventListener('change', (e) => {
this.radius = parseInt(e.target.value, 10);
document.getElementById('radius-label').textContent = this.radius + ' км';
this.refreshNearby();
});
document.getElementById('loc-share-btn').onclick = (e) => {
e.preventDefault();
this.requestLocation();
};
// Если уже есть сохранённые координаты — покажем на карте
if (window._lastKnownPos) {
this.applyPosition(window._lastKnownPos);
}
},
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.textContent = '📍 Обновить геолокацию'; }
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.textContent = '📍 Попробовать снова'; }
if (status) status.textContent = '⚠️ ' + this.geolocationErrorText(err);
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);
}
},
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: `<div style="width:36px;height:36px;border-radius:50%;background:#FF6B35;color:white;display:flex;align-items:center;justify-content:center;font-weight:700;border:2px solid white;">${escapeHtml((n.name || '?')[0])}</div>`,
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) {
console.warn(e);
}
},
};
window.MapView = MapView;