From 042dc1a5d33c0b9d032297dd4014b1bb7cf76f1d Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Mon, 18 May 2026 15:35:21 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=B5=D1=82=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20+=20=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=B5=D0=B7=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - assembly_detail.js: экран #/c/assembly/:id — статус, адрес, фото, подпись, gcal - orders.js: сборки кликабельны → #/c/assembly/:id - app.js: маршрут #/c/assembly/ - selfmeasure.js: цена 2500₽ + 40₽/км за КАД на шаге 1 и шаге 5 - cabinet.js: цена под кнопкой самозамера Co-Authored-By: Claude Sonnet 4.6 --- miniapp/assets/app.js | 4 + miniapp/assets/assembly_detail.js | 174 ++++++++++++++++++++++++++++++ miniapp/assets/cabinet.js | 4 + miniapp/assets/orders.js | 2 +- miniapp/assets/selfmeasure.js | 17 ++- miniapp/index.html | 1 + 6 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 miniapp/assets/assembly_detail.js diff --git a/miniapp/assets/app.js b/miniapp/assets/app.js index 847cbfa..783676d 100644 --- a/miniapp/assets/app.js +++ b/miniapp/assets/app.js @@ -1722,6 +1722,10 @@ function routeByHash() { } else if (location.hash === "#/c/orders") { if (typeof OrdersScreen !== "undefined") OrdersScreen.mount(app); else init(); + } else if (location.hash.startsWith("#/c/assembly/")) { + const assemblyId = decodeURIComponent(location.hash.replace("#/c/assembly/", "")); + if (typeof AssemblyDetailScreen !== "undefined") AssemblyDetailScreen.mount(app, assemblyId); + else init(); } else if (location.hash === "#/c/selfmeasure") { if (typeof SelfMeasureScreen !== "undefined") SelfMeasureScreen.mount(app); else init(); diff --git a/miniapp/assets/assembly_detail.js b/miniapp/assets/assembly_detail.js new file mode 100644 index 0000000..4abeaef --- /dev/null +++ b/miniapp/assets/assembly_detail.js @@ -0,0 +1,174 @@ +/* ============================================================ + Детальная карточка сборки — #/c/assembly/:id + Доступна клиенту, менеджеру, мастеру. + ============================================================ */ + +const AssemblyDetailScreen = (function () { + + function escHtml(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + function fmtDate(iso) { + if (!iso) return null; + try { + return new Date(iso).toLocaleDateString("ru-RU", { + day: "numeric", month: "long", year: "numeric", + hour: "2-digit", minute: "2-digit" + }); + } catch { return iso.slice(0, 16).replace("T", " "); } + } + + async function _api(path, body = {}) { + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), 15000); + try { + const res = await fetch(`${BACKEND_URL}/api/${path}`, { + method: "POST", signal: ctrl.signal, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, ...body }), + }); + if (!res.ok) throw new Error(`Ошибка сервера (${res.status})`); + return await res.json(); + } catch (e) { + if (e.name === "AbortError") throw new Error("Сервер не отвечает"); + throw e; + } finally { clearTimeout(t); } + } + + const STATUS = { + created: { icon: "🆕", text: "Создана", color: "#8e8e8e" }, + scheduled: { icon: "📅", text: "Запланирована", color: "#2980B9" }, + in_progress: { icon: "🔨", text: "В процессе", color: "#F39C12" }, + done: { icon: "✅", text: "Завершена", color: "#27AE60" }, + cancelled: { icon: "❌", text: "Отменена", color: "#C0392B" }, + }; + + function row(label, value, opts = {}) { + if (!value) return ""; + return ` +
+
${escHtml(label)}
+
${opts.html ? value : escHtml(value)}
+
`; + } + + async function mount(container, assemblyId) { + container.innerHTML = ""; + document.body.classList.remove("has-bottom-nav"); + const oldNav = document.getElementById("bottom-nav"); + if (oldNav) oldNav.remove(); + + // Header + 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); + + const screen = document.createElement("div"); + screen.className = "podbor-screen"; + screen.innerHTML = `
`; + container.appendChild(screen); + + try { + const data = await _api("assembly_detail", { assembly_id: assemblyId }); + + if (data.error) { + screen.innerHTML = `
${escHtml(data.error)}
`; + return; + } + + const sl = STATUS[data.status] || { icon: "🔧", text: data.status, color: "#8e8e8e" }; + + // Статус-баннер + const statusBanner = ` +
+
${sl.icon}
+
+
${escHtml(sl.text)}
+
ID: ${escHtml(data.id)}
+
+
`; + + // Основные данные + const mainBlock = ` +
+ ${row("Адрес", data.address)} + ${row("Объём работ", data.scope_of_work)} + ${row("Дата сборки", fmtDate(data.scheduled_at))} + ${row("Начало", fmtDate(data.started_at))} + ${row("Завершение", fmtDate(data.completed_at))} +
`; + + // Заметка менеджера + const noteBlock = data.manager_note ? ` +
+
Заметка
+
${escHtml(data.manager_note)}
+
` : ""; + + // Фото результата + const photosAfter = (data.photos_after || []).filter(Boolean); + const photosBlock = photosAfter.length ? ` +
+
Фото результата
+
+ ${photosAfter.map(u => ` + + фото + `).join("")} +
+
` : ""; + + // Подпись + const signBlock = data.signed_by_name ? ` +
+
+
Принято клиентом
+
${escHtml(data.signed_by_name)}
+
+
${escHtml(fmtDate(data.signed_at) || "")}
+
` : ""; + + // Кнопка Google Calendar + const calBtn = data.gcal_event_url ? ` +
+ + 📅 Посмотреть в Google Календаре + +
` : ""; + + screen.innerHTML = statusBanner + mainBlock + noteBlock + photosBlock + signBlock + calBtn + + `
`; + + } catch (e) { + screen.innerHTML = `
Ошибка: ${escHtml(e.message)}
`; + } + } + + return { mount }; +})(); diff --git a/miniapp/assets/cabinet.js b/miniapp/assets/cabinet.js index ab04400..9373752 100644 --- a/miniapp/assets/cabinet.js +++ b/miniapp/assets/cabinet.js @@ -187,6 +187,10 @@ const CabinetScreen = (function () {
+
+ Выезд специалиста: 2 500 ₽ + · за КАД СПб +40 ₽/км +
`; diff --git a/miniapp/assets/orders.js b/miniapp/assets/orders.js index 1b4420c..fcdbed5 100644 --- a/miniapp/assets/orders.js +++ b/miniapp/assets/orders.js @@ -119,7 +119,7 @@ const OrdersScreen = (function () { subtitle: a.scope_of_work || null, statusText: sl.text, statusColor: sl.color, - href: null, + href: a.id ? `#/c/assembly/${encodeURIComponent(a.id)}` : null, calUrl: a.gcal_event_url || null, }; } diff --git a/miniapp/assets/selfmeasure.js b/miniapp/assets/selfmeasure.js index 11ba3b5..948d917 100644 --- a/miniapp/assets/selfmeasure.js +++ b/miniapp/assets/selfmeasure.js @@ -134,11 +134,23 @@ const SelfMeasureScreen = (function () { return ["А"]; } + /* ---- Price info block ---- */ + const PRICE_INFO_HTML = ` +
+
💰 Стоимость выезда специалиста
+
+ В черте КАД Санкт-Петербурга — 2 500 ₽
+ За пределами КАД — 2 500 ₽ + 40 ₽/км от кольцевой до адреса +
+
`; + /* ---- Step 1: Kitchen type ---- */ function renderStep1(state) { const wrap = document.createElement("div"); wrap.innerHTML = ` -
+ ${PRICE_INFO_HTML} +
Выберите тип кухни
`; @@ -526,7 +538,8 @@ const SelfMeasureScreen = (function () { const wrap = document.createElement("div"); wrap.innerHTML = ` -
+ ${PRICE_INFO_HTML} +
Контактные данные
diff --git a/miniapp/index.html b/miniapp/index.html index f17c5aa..fe7a1fe 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -50,6 +50,7 @@ +