diff --git a/public/index.html b/public/index.html index 1b54461..6127380 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ - + @@ -34,15 +34,15 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/public/js/api.js b/public/js/api.js index 3b80112..4f1c70c 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -103,6 +103,13 @@ const Auth = { getUser: function (id) { return apiGet('/api/v1/users/' + id); }, }; +// === API namespaces === +const UserApi = { + get: function (userId) { return apiGet('/api/v1/users/' + userId); }, + reviews: function (userId) { return apiGet('/api/v1/users/' + userId + '/reviews'); }, + stats: function (userId) { return apiGet('/api/v1/users/' + userId + '/stats'); }, +}; + const ChatApi = { ensureChat: function (otherId) { return apiPost('/api/v1/chats', { other_id: otherId }); }, listChats: function () { return apiGet('/api/v1/chats'); }, diff --git a/public/js/views/profile.js b/public/js/views/profile.js index 312dd37..0dd6533 100644 --- a/public/js/views/profile.js +++ b/public/js/views/profile.js @@ -198,4 +198,91 @@ const ProfileView = { }, }; -window.ProfileView = ProfileView; \ No newline at end of file +window.ProfileView = ProfileView; + +// UserProfileView — чужой профиль (read-only) — открывается по тапу на маркер на карте +const UserProfileView = { + userId: null, + userName: null, + data: null, + + async render(root, params) { + this.userId = params.userId; + this.userName = params.name || 'Пользователь'; + root.innerHTML = ` +
+

👤 ${escapeHtml(this.userName)}

+
${'
⏳ Загружаю…
'}
+
+ `; + try { + const [user, stats] = await Promise.all([ + API.UserApi.get(this.userId), + API.ReviewApi.stats(this.userId).catch(() => ({})), + ]); + this.data = user.user || user; + const u = this.data; + const init = ((u.name || u.email || '?')[0] || '?').toUpperCase(); + const rating = stats.rating_avg ? Number(stats.rating_avg).toFixed(1) : '—'; + const reviewsCount = stats.rating_count || 0; + + document.getElementById('up-content').innerHTML = ` + +
+
${u.photo_url ? '' : init}
+
+
${escapeHtml(u.name || '')}
+ ${u.city ? `
📍 ${escapeHtml(u.city)}
` : ''} + ${reviewsCount > 0 ? `
★ ${rating} · ${reviewsCount} ${pluralRu(reviewsCount, 'отзыв', 'отзыва', 'отзывов')}
` : ''} +
+
+ + + ${u.bio ? ` +
+
О себе
+
${escapeHtml(u.bio)}
+
+ ` : ''} + + +
+ + ${reviewsCount > 0 ? `` : ''} +
+ `; + + const msgBtn = document.getElementById('up-message'); + if (msgBtn) msgBtn.onclick = async () => { + msgBtn.className = 'btn-b load'; + msgBtn.textContent = '⏳ Открываем…'; + try { + const r = await API.ChatApi.ensureChat(this.userId); + const cid = r.chat_id || r.chat?.id; + if (!cid) throw new Error('chat not created'); + App.openChat(cid, this.userId, u.name); + } catch (e) { + msgBtn.className = 'btn-b err'; + msgBtn.textContent = '⚠️ ' + (e.message || 'Ошибка'); + App._toast('Не удалось открыть чат', 'err'); + setTimeout(() => { msgBtn.className = 'btn-b'; msgBtn.textContent = '💬 Написать'; }, 2000); + } + }; + + const revBtn = document.getElementById('up-reviews'); + if (revBtn) revBtn.onclick = () => UserReviewsView.render(root, { userId: this.userId, userName: u.name }); + } catch (e) { + document.getElementById('up-content').innerHTML = `
⚠️ Не удалось загрузить
`; + App._toast('Ошибка загрузки профиля', 'err'); + } + }, +}; + +window.UserProfileView = UserProfileView; + +function pluralRu(n, one, few, many) { + const mod10 = n % 10, mod100 = n % 100; + if (mod10 === 1 && mod100 !== 11) return one; + if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return few; + return many; +} \ No newline at end of file