diff --git a/public/css/app.css b/public/css/app.css index 78901de..43071a4 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -482,4 +482,21 @@ label { font-size: 13px; color: var(--text-dim); margin-bottom: 4px; display: bl .onboarding-icon, .onboarding-dot { animation: none !important; transition: none !important; } .onboarding-slide { transform: none; opacity: 1; pointer-events: auto; } -} \ No newline at end of file +} +/* Pulse animation для важных кнопок (позвать на действие) */ +.btn-pulse { + display: inline-block; + animation: btn-pulse 2s ease-in-out infinite; +} +@keyframes btn-pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.2); } +} + +/* Read-marks для чата */ +.bubble .meta { display: flex; align-items: center; gap: 4px; } +.bubble .read-tick { font-size: 12px; opacity: 0.85; } +.bubble .read-tick.read { color: var(--secondary); font-weight: 700; } + +/* edit marker */ +.bubble .edited { font-style: italic; opacity: 0.7; } diff --git a/public/js/views/chat.js b/public/js/views/chat.js index 0f85d3a..49d8a08 100644 --- a/public/js/views/chat.js +++ b/public/js/views/chat.js @@ -23,7 +23,7 @@ const ChatView = {
- +
@@ -33,6 +33,14 @@ const ChatView = { document.getElementById('chat-profile').onclick = () => App.openUserProfile(this.otherId, this.otherName); document.getElementById('chat-review').onclick = () => App.openReview(this.chatId, this.otherId, this.otherName); + document.getElementById('msg-input').addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + this.send(); + } + }); + document.getElementById('msg-send').onclick = () => this.send(); + this.loadMessages(); this.setupWS(); this.markRead(); @@ -59,11 +67,16 @@ const ChatView = { if (m.deleted) { div.innerHTML = 'Удалено'; } else { + // ✓✓ если прочитано собеседником, ✓ если только доставлено + const readMark = me + ? (m.read_at ? '✓✓' : '') + : ''; div.innerHTML = `
${escapeHtml(m.body)}
${formatTime(m.created_at)} ${m.edited ? 'ред.' : ''} + ${readMark}
`; if (me) { @@ -103,6 +116,8 @@ const ChatView = { this.appendMessage(d.payload); } else if (d.type === 'message_edited' && d.payload.chat_id === this.chatId) { this.updateMessage(d.payload); + } else if (d.type === 'message_read' && d.payload.chat_id === this.chatId) { + this.markMessagesRead(); } } catch {} }; @@ -113,6 +128,18 @@ const ChatView = { } }, + async markMessagesRead() { + try { + const r = await API.ChatApi.listMessages(this.chatId); + r.messages.forEach((m) => { + if (m.read_at) { + const el = document.querySelector(`.bubble[data-id="${m.id}"] .read-tick`); + if (el) { el.classList.add('read'); el.textContent = '✓✓'; } + } + }); + } catch {} + }, + updateMessage(p) { const el = document.querySelector(`.bubble[data-id="${p.id}"]`); if (el) { @@ -134,7 +161,6 @@ const ChatView = { inp.value = ''; try { await API.ChatApi.sendMessage(this.chatId, body); - // WS пришлёт обратно } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } @@ -149,4 +175,4 @@ const ChatView = { }, }; -window.ChatView = ChatView; \ No newline at end of file +window.ChatView = ChatView; diff --git a/public/js/views/map.js b/public/js/views/map.js index 1d7fe08..abdc359 100644 --- a/public/js/views/map.js +++ b/public/js/views/map.js @@ -21,7 +21,9 @@ const MapView = { - +
Нажми чтобы получить координаты
@@ -52,17 +54,21 @@ const MapView = { this.visible = e.target.checked; try { await API.Auth.setVisibility(this.visible); - this.refreshLocation(); + if (window._lastKnownPos) this.applyPosition(window._lastKnownPos); } catch (err) { - API.Store && console.warn(err); + console.warn(err); TG_APP.showAlert('Не удалось обновить видимость'); } }); - document.getElementById('radius-sel').addEventListener('change', (e) => { + document.getElementById('radius-sel').addEventListener('change', async (e) => { this.radius = parseInt(e.target.value, 10); document.getElementById('radius-label').textContent = this.radius + ' км'; - this.refreshNearby(); + const c = document.getElementById('nearby-count'); + if (c) c.textContent = '⏳ поиск...'; + if (window._lastKnownPos) { + await this.refreshNearbyFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude); + } }); document.getElementById('loc-share-btn').onclick = (e) => { @@ -73,6 +79,10 @@ const MapView = { // Если уже есть сохранённые координаты — покажем на карте if (window._lastKnownPos) { this.applyPosition(window._lastKnownPos); + } else { + // Авто-запрос геолокации при первом заходе на карту + // Не подвешиваем UI — requestLocation вернётся мгновенно, а браузер сам спросит у пользователя + this.requestLocation(); } }, @@ -90,13 +100,24 @@ const MapView = { navigator.geolocation.getCurrentPosition( async (pos) => { window._lastKnownPos = pos; - if (btn) { btn.disabled = false; btn.textContent = '📍 Обновить геолокацию'; } + 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.textContent = '📍 Попробовать снова'; } - if (status) status.textContent = '⚠️ ' + this.geolocationErrorText(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 } @@ -105,7 +126,7 @@ const MapView = { geolocationErrorText(err) { switch (err.code) { - case err.PERMISSION_DENIED: return 'Доступ к геолокации запрещён. Разреши в настройках браузера.'; + case err.PERMISSION_DENIED: return 'Доступ к геолокации запрещён.'; case err.POSITION_UNAVAILABLE: return 'Не удалось определить координаты (нет GPS/Wi-Fi).'; case err.TIMEOUT: return 'Таймаут запроса геолокации.'; default: return 'Неизвестная ошибка геолокации.'; @@ -164,9 +185,11 @@ const MapView = { this.markers.push(m); }); } catch (e) { + const c = document.getElementById('nearby-count'); + if (c) c.textContent = '📍 нужна геолокация'; console.warn(e); } }, }; -window.MapView = MapView; \ No newline at end of file +window.MapView = MapView;