mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 15:44:47 +00:00
231 lines
7.8 KiB
JavaScript
231 lines
7.8 KiB
JavaScript
// ЗОВ MiniApp — главный скрипт.
|
||
// На входе: подписанный initData от Telegram.
|
||
// Ходим на backend → получаем профиль (роль, статус) → рендерим меню.
|
||
|
||
const tg = window.Telegram?.WebApp;
|
||
const BACKEND_URL = "https://script.google.com/macros/s/AKfycbyxSwfD4hi5Y176nKV3tmnQq21kCQM3BBm34WGgObgAuybsAW7WPEuxlrPZ1a16viK3/exec";
|
||
|
||
const app = document.getElementById("app");
|
||
|
||
/* ----------------- Telegram WebApp setup ----------------- */
|
||
function setupTelegram() {
|
||
if (!tg) return;
|
||
try {
|
||
tg.ready();
|
||
tg.expand();
|
||
if (tg.setHeaderColor) tg.setHeaderColor("#003E7E");
|
||
if (tg.setBackgroundColor) tg.setBackgroundColor("#F4F4F5");
|
||
if (tg.enableClosingConfirmation) tg.enableClosingConfirmation();
|
||
} catch (e) { console.warn(e); }
|
||
}
|
||
|
||
function haptic(type = "selection") {
|
||
try {
|
||
if (!tg?.HapticFeedback) return;
|
||
if (type === "impact") tg.HapticFeedback.impactOccurred("light");
|
||
else if (type === "success") tg.HapticFeedback.notificationOccurred("success");
|
||
else tg.HapticFeedback.selectionChanged();
|
||
} catch (e) {}
|
||
}
|
||
|
||
/* ----------------- Data ----------------- */
|
||
async function fetchMe() {
|
||
if (!BACKEND_URL) {
|
||
// dev-режим без backend — мок для просмотра вёрстки
|
||
return {
|
||
role: "manager",
|
||
user: {
|
||
full_name: "Руслан Васильев",
|
||
salon: "ЗОВ Москва",
|
||
avatar_initial: "Р",
|
||
},
|
||
status: "active",
|
||
status_until: "12.08.2026",
|
||
};
|
||
}
|
||
// Apps Script Web App: путь через query-параметр.
|
||
// Заголовок Content-Type НЕ ставим — иначе браузер шлёт CORS preflight,
|
||
// который Apps Script не обрабатывает. Без заголовка fetch использует
|
||
// text/plain — Apps Script всё равно парсит body как JSON.
|
||
const res = await fetch(`${BACKEND_URL}?path=me`, {
|
||
method: "POST",
|
||
body: JSON.stringify({
|
||
initData: tg?.initData || "",
|
||
startParam: tg?.initDataUnsafe?.start_param || null,
|
||
}),
|
||
});
|
||
if (!res.ok) throw new Error("backend HTTP " + res.status);
|
||
return res.json();
|
||
}
|
||
|
||
/* ----------------- Helpers ----------------- */
|
||
function el(html) {
|
||
const t = document.createElement("template");
|
||
t.innerHTML = html.trim();
|
||
return t.content.firstChild;
|
||
}
|
||
|
||
function statusLabel(s) {
|
||
return ({
|
||
active: "Доступ открыт",
|
||
lapsed: "Доступ ограничен",
|
||
grace: "Грейс-период",
|
||
})[s] || s;
|
||
}
|
||
|
||
function getInitial(name) {
|
||
return (name || "").trim().slice(0, 1).toUpperCase() || "?";
|
||
}
|
||
|
||
/* ----------------- Renders ----------------- */
|
||
function renderManager(me) {
|
||
const status = me.status || "active";
|
||
const statusUntil = me.status_until ? `до ${me.status_until}` : "";
|
||
const initial = me.user?.avatar_initial || getInitial(me.user?.full_name);
|
||
|
||
app.innerHTML = "";
|
||
|
||
app.appendChild(el(`
|
||
<header class="profile-card">
|
||
<div class="avatar">${initial}</div>
|
||
<div class="info">
|
||
<div class="role-tag">Менеджер</div>
|
||
<div class="name">${me.user?.full_name || ""}</div>
|
||
<div class="meta">${me.user?.salon || ""}</div>
|
||
<span class="status-row">
|
||
<span class="status-dot ${status}"></span>
|
||
<span>${statusLabel(status)}${statusUntil ? " · " + statusUntil : ""}</span>
|
||
</span>
|
||
</div>
|
||
</header>
|
||
`));
|
||
|
||
const sections = [
|
||
{
|
||
label: "Работа с клиентами",
|
||
items: [
|
||
{ icon: "wrench", color: "green", label: "Подбор техники для клиента", href: "#/m/podbor" },
|
||
{ icon: "ruler", color: "blue", label: "Замеры", href: "#/m/measurements" },
|
||
{ icon: "clipboard", color: "gold", label: "Заявки клиентов", soon: true },
|
||
{ icon: "briefcase", color: "gray", label: "Сделки", soon: true },
|
||
],
|
||
},
|
||
{
|
||
label: "Аккаунт",
|
||
items: [
|
||
{ icon: "wallet", color: "gold", label: "Мой статус и доступ", href: "#/m/status" },
|
||
{ icon: "help", color: "blue", label: "Связь с куратором", href: "#/m/help" },
|
||
],
|
||
},
|
||
];
|
||
|
||
sections.forEach(section => {
|
||
app.appendChild(el(`<div class="section-label">${section.label}</div>`));
|
||
app.appendChild(buildMenu(section.items));
|
||
});
|
||
|
||
app.appendChild(el(`
|
||
<div class="footer-hint">
|
||
Куратор партнёрской сети — Руслан Васильев<br>
|
||
<a href="https://t.me/wasrusgen">@wasrusgen</a>
|
||
</div>
|
||
`));
|
||
}
|
||
|
||
function renderClient(me) {
|
||
const initial = me.user?.avatar_initial || getInitial(me.user?.full_name) || "?";
|
||
const greetName = me.user?.full_name || "Здравствуйте";
|
||
|
||
app.innerHTML = "";
|
||
|
||
app.appendChild(el(`
|
||
<header class="profile-card">
|
||
<div class="avatar">${initial}</div>
|
||
<div class="info">
|
||
<div class="role-tag">Клиент</div>
|
||
<div class="name">${greetName}</div>
|
||
<div class="meta">${me.manager ? "Менеджер: " + me.manager.full_name + (me.manager.salon ? ", " + me.manager.salon : "") : "ЗОВ — кухонная мебель"}</div>
|
||
</div>
|
||
</header>
|
||
`));
|
||
|
||
const sections = [
|
||
{
|
||
label: "Подобрать кухню",
|
||
items: [
|
||
{ icon: "ruler", color: "blue", label: "Замер кухни", href: "#/c/measure" },
|
||
{ icon: "wrench", color: "green", label: "Подобрать технику", soon: true },
|
||
{ icon: "wallet", color: "gold", label: "Калькулятор бюджета", soon: true },
|
||
],
|
||
},
|
||
{
|
||
label: "Помощь",
|
||
items: [
|
||
{ icon: "lightbulb", color: "gold", label: "Идеи и кейсы", soon: true },
|
||
{ icon: "phone", color: "blue", label: "Связаться с менеджером", href: "#/c/contact" },
|
||
{ icon: "pin", color: "green", label: "Записаться в салон", soon: true },
|
||
],
|
||
},
|
||
];
|
||
|
||
sections.forEach(section => {
|
||
app.appendChild(el(`<div class="section-label">${section.label}</div>`));
|
||
app.appendChild(buildMenu(section.items));
|
||
});
|
||
|
||
app.appendChild(el(`
|
||
<div class="footer-hint">
|
||
Фабрика кухонной мебели <strong>ЗОВ</strong><br>
|
||
<a href="https://t.me/wasrusgen1">канал @wasrusgen1</a>
|
||
</div>
|
||
`));
|
||
}
|
||
|
||
function buildMenu(items) {
|
||
const menu = el(`<nav class="menu"></nav>`);
|
||
items.forEach(item => {
|
||
const cls = item.soon ? "menu-item disabled" : "menu-item";
|
||
const node = el(`
|
||
<a class="${cls}" ${item.href && !item.soon ? `href="${item.href}"` : ""}>
|
||
<div class="icon ${item.color}">${ICONS[item.icon] || ""}</div>
|
||
<div class="text">
|
||
<div class="label">
|
||
${item.label}
|
||
${item.soon ? '<span class="badge">скоро</span>' : ""}
|
||
</div>
|
||
${item.sub ? `<div class="sub">${item.sub}</div>` : ""}
|
||
</div>
|
||
${item.soon ? "" : `<div class="chevron">${ICONS.chevron}</div>`}
|
||
</a>
|
||
`);
|
||
if (!item.soon) node.addEventListener("click", () => haptic("impact"));
|
||
menu.appendChild(node);
|
||
});
|
||
return menu;
|
||
}
|
||
|
||
function renderError() {
|
||
app.innerHTML = "";
|
||
app.appendChild(el(`
|
||
<div class="error">
|
||
<h3>Не удалось загрузить кабинет</h3>
|
||
<div>Проверьте подключение и попробуйте позже.</div>
|
||
</div>
|
||
`));
|
||
}
|
||
|
||
/* ----------------- Init ----------------- */
|
||
async function init() {
|
||
setupTelegram();
|
||
try {
|
||
const me = await fetchMe();
|
||
if (me.role === "manager") renderManager(me);
|
||
else renderClient(me);
|
||
} catch (e) {
|
||
console.error(e);
|
||
renderError();
|
||
}
|
||
}
|
||
|
||
init();
|