From bdbdf4b25983467a46fe81b0cf7f7d004be75543 Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Mon, 18 May 2026 12:17:52 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20#/me?= =?UTF-8?q?=20=E2=80=94=20=D0=9C=D0=BE=D0=B9=20=D0=BF=D1=80=D0=BE=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D1=85=20=D1=80=D0=BE=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit manager: статус доступа, салон, быстрые переходы staff: роли-чипы, кнопки на замеры/сборки client: менеджер, кнопки подбора и сборок Co-Authored-By: Claude Sonnet 4.6 --- miniapp/assets/app.js | 3 + miniapp/assets/me.js | 214 ++++++++++++++++++++++++++++++++++++++++++ miniapp/index.html | 3 +- 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 miniapp/assets/me.js diff --git a/miniapp/assets/app.js b/miniapp/assets/app.js index 54de0ce..cb748a2 100644 --- a/miniapp/assets/app.js +++ b/miniapp/assets/app.js @@ -1746,6 +1746,9 @@ function routeByHash() { } else if (location.hash.startsWith("#/master")) { const me = window.__zovMe; if (me) renderStaff(me); else init(); + } else if (location.hash.startsWith("#/me")) { + if (typeof MeScreen !== "undefined") MeScreen.mount(app); + else init(); } else if (location.hash.startsWith("#/c/proposal")) { app.innerHTML = ""; document.body.classList.remove("has-bottom-nav"); diff --git a/miniapp/assets/me.js b/miniapp/assets/me.js new file mode 100644 index 0000000..e49a8c0 --- /dev/null +++ b/miniapp/assets/me.js @@ -0,0 +1,214 @@ +/* ============================================================ + Экран «Мой профиль» — #/me + Работает для всех ролей: manager, staff, client + ============================================================ */ + +const MeScreen = (function () { + + function escHtml(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + async function _fetchWithTimeout(url, body, ms = 15000) { + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), ms); + try { + const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) }); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает"); + throw e; + } finally { clearTimeout(t); } + } + + function header(container) { + const h = document.createElement("header"); + h.className = "podbor-header"; + h.innerHTML = ` + +
Мой профиль
+
+ `; + h.querySelector(".podbor-back").addEventListener("click", () => { + haptic && haptic("impact"); + history.back(); + }); + container.appendChild(h); + } + + function avatarBlock(initial, name, subtitle) { + return ` +
+
+ ${escHtml(initial)} +
+
+
${escHtml(name)}
+ ${subtitle ? `
${escHtml(subtitle)}
` : ""} +
+
+ `; + } + + function roleChip(label, color) { + const colors = { gold: "#C5A55E", blue: "#3D7AB5", green: "#4A9E6A", muted: "var(--muted)" }; + const c = colors[color] || colors.gold; + return ` + ${escHtml(label)} + `; + } + + function renderManager(container, me) { + const u = me.user || {}; + const statusLabel = me.status === "active" ? "✅ Активен" : + me.status === "trial" ? "🟡 Пробный" : "🔴 Неактивен"; + const statusColor = me.status === "active" ? "green" : + me.status === "trial" ? "gold" : "muted"; + + const screen = document.createElement("div"); + screen.className = "podbor-screen"; + screen.innerHTML = ` + ${avatarBlock(u.avatar_initial || "?", u.full_name || "Менеджер", u.salon || "")} + +
+
Статус доступа
+
+ Статус + ${roleChip(statusLabel, statusColor)} +
+ ${me.status_until ? `
Активен до${escHtml(me.status_until)}
` : ""} + ${u.salon ? `
Салон${escHtml(u.salon)}
` : ""} +
+ +
+
Быстрый переход
+
+ + + + +
+
+ `; + screen.querySelectorAll("[data-href]").forEach(btn => { + btn.addEventListener("click", () => { + haptic && haptic("impact"); + location.hash = btn.dataset.href; + }); + }); + container.appendChild(screen); + } + + function renderStaffMe(container, me) { + const u = me.user || {}; + const caps = me.capabilities || {}; + const chips = [ + caps.measurer && roleChip("замерщик", "blue"), + caps.assembler && roleChip("сборщик", "green"), + ].filter(Boolean).join(""); + + const screen = document.createElement("div"); + screen.className = "podbor-screen"; + screen.innerHTML = ` + ${avatarBlock(u.avatar_initial || "?", u.full_name || "Сотрудник", "")} + +
${chips}
+ +
+
Мои задачи
+
+ ${caps.measurer ? `` : ""} + ${caps.measurer ? `` : ""} + ${caps.assembler ? `` : ""} +
+
+ `; + screen.querySelectorAll("[data-href]").forEach(btn => { + btn.addEventListener("click", () => { + haptic && haptic("impact"); + location.hash = btn.dataset.href; + }); + }); + container.appendChild(screen); + } + + function renderClientMe(container, me) { + const u = me.user || {}; + const mgr = me.manager || {}; + + const screen = document.createElement("div"); + screen.className = "podbor-screen"; + screen.innerHTML = ` + ${avatarBlock(u.avatar_initial || "?", u.full_name || "Клиент", "Личный кабинет")} + + ${mgr.full_name ? ` +
+
Мой менеджер
+
Имя${escHtml(mgr.full_name)}
+ ${mgr.salon ? `
Салон${escHtml(mgr.salon)}
` : ""} +
+ ` : ""} + +
+
+ + +
+
+ `; + screen.querySelectorAll("[data-href]").forEach(btn => { + btn.addEventListener("click", () => { + haptic && haptic("impact"); + location.hash = btn.dataset.href; + }); + }); + container.appendChild(screen); + } + + async function mount(container) { + container.innerHTML = ""; + document.body.classList.remove("has-bottom-nav"); + const oldNav = document.getElementById("bottom-nav"); + if (oldNav) oldNav.remove(); + + header(container); + + const loading = document.createElement("div"); + loading.className = "loader-inline"; + loading.innerHTML = `
`; + container.appendChild(loading); + + try { + const me = await _fetchWithTimeout(`${BACKEND_URL}/api/me`, { + initData: tg?.initData || "", + initDataUnsafe: tg?.initDataUnsafe || null, + }); + loading.remove(); + + if (me.error) { + container.appendChild(el(`
${escHtml(me.error)}
`)); + return; + } + + const role = me.role; + if (role === "manager" || me.roles?.includes("manager")) { + renderManager(container, me); + } else if (role === "staff") { + renderStaffMe(container, me); + } else { + renderClientMe(container, me); + } + } catch (e) { + loading.remove(); + container.appendChild(el(`
Ошибка: ${escHtml(e.message)}
`)); + } + } + + return { mount }; +})(); diff --git a/miniapp/index.html b/miniapp/index.html index aaadc24..9847874 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -45,6 +45,7 @@ - + +