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 @@
-
+
+