/* ============================================================ Сборка (Phase 4) — менеджер создаёт заявку на сборку, мастер исполняет, клиент подписывает приёмку. Этап 1: создание + список + детальная. ============================================================ */ const Assembly = (function () { let root = null; let state = { client_name: "", client_phone: "", address: "", scope_of_work: "", measurement_id: "", lead_id: "", scheduled_at: "", manager_note: "", }; function mount(container) { root = container; document.body.classList.remove("has-bottom-nav"); const oldNav = document.getElementById("bottom-nav"); if (oldNav) oldNav.remove(); const hash = location.hash || ""; // #/assembly/new — форма создания // #/assembly/ — детальная карточка if (hash === "#/assembly/new" || hash.startsWith("#/assembly/new?")) { resetState(); prefillFromSession(); renderForm(); } else if (hash.startsWith("#/assembly/")) { const id = hash.replace("#/assembly/", "").split("?")[0]; renderDetail(id); } else { // Список (для мастера) renderList(); } } function resetState() { state = { client_name: "", client_phone: "", address: "", scope_of_work: "", measurement_id: "", lead_id: "", scheduled_at: "", manager_note: "", }; } function prefillFromSession() { try { const raw = sessionStorage.getItem("prefillAssembly"); if (raw) { const pre = JSON.parse(raw); if (pre.name) state.client_name = pre.name; if (pre.phone) state.client_phone = pre.phone; if (pre.address) state.address = pre.address; if (pre.measurement_id) state.measurement_id = pre.measurement_id; if (pre.lead_id) state.lead_id = pre.lead_id; sessionStorage.removeItem("prefillAssembly"); } } catch (e) {} } function headerEl(title, backHash) { const h = el(`
${escHtml(title)}
`); h.querySelector(".podbor-back").addEventListener("click", () => { if (backHash) location.hash = backHash; else history.back(); }); return h; } /* ===================== Форма создания ===================== */ function renderForm() { if (!root) return; root.innerHTML = ""; root.appendChild(headerEl("Заказать сборку", "")); const form = el(`

Новая
сборка

Опишите состав работ — мастер получит карточку с адресом и датой.

`); bindInputs(form); form.querySelector("#submitBtn").addEventListener("click", () => onSubmit(form)); root.appendChild(form); } function bindInputs(node) { node.querySelectorAll("[data-bind]").forEach(input => { input.addEventListener("input", () => { state[input.dataset.bind] = input.value; }); }); } async function onSubmit(form) { const btn = form.querySelector("#submitBtn"); const result = form.querySelector("#submitResult"); result.innerHTML = ""; form.querySelectorAll(".field-error").forEach(e => e.textContent = ""); let ok = true; if (!state.client_name.trim()) { form.querySelector("#errName").textContent = "Укажите имя клиента"; ok = false; } if (!state.address.trim()) { form.querySelector("#errAddress").textContent = "Укажите адрес сборки"; ok = false; } if (!state.scope_of_work.trim()) { form.querySelector("#errScope").textContent = "Опишите состав работ"; ok = false; } if (!ok) return; btn.disabled = true; btn.innerHTML = ' сохраняем...'; const body = { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, client_name: state.client_name.trim(), client_phone: state.client_phone.trim(), address: state.address.trim(), scope_of_work: state.scope_of_work.trim(), measurement_id: state.measurement_id, lead_id: state.lead_id, scheduled_at: state.scheduled_at ? new Date(state.scheduled_at).toISOString() : "", manager_note: state.manager_note.trim(), }; try { const res = await fetch(`${BACKEND_URL}/api/assembly_create`, { method: "POST", body: JSON.stringify(body), }); const data = await res.json(); if (data.error) { result.innerHTML = `
Ошибка: ${escHtml(data.error)}
`; btn.disabled = false; btn.textContent = "Заказать сборку"; return; } haptic && haptic("success"); result.innerHTML = `
${ICONS.check || "✓"}
Сборка заведена
ID #${(data.id || "").slice(0, 6)} · ${data.status === "scheduled" ? "дата назначена" : "без даты"}
`; btn.style.display = "none"; result.querySelector("#toHome")?.addEventListener("click", () => { location.hash = ""; if (typeof routeByHash === "function") routeByHash(); }); result.querySelector("#toDetail")?.addEventListener("click", () => { location.hash = `#/assembly/${data.id}`; }); } catch (e) { result.innerHTML = `
Сеть: ${escHtml(e.message)}
`; btn.disabled = false; btn.textContent = "Заказать сборку"; } } /* ===================== Список сборок ===================== */ async function renderList() { if (!root) return; root.innerHTML = ""; root.appendChild(headerEl("Сборки", "")); const loading = el(`
`); root.appendChild(loading); try { const res = await fetch(`${BACKEND_URL}/api/assembly_list`, { method: "POST", body: JSON.stringify({ initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, }), }); const data = await res.json(); loading.remove(); if (data.error) { root.appendChild(el(`
${escHtml(data.error)}
`)); return; } const items = data.assemblies || []; if (!items.length) { root.appendChild(el(`
Сборок пока нет
`)); return; } const list = el(`
`); for (const a of items) { const dateStr = a.scheduled_at ? formatDateHuman(a.scheduled_at) : "—"; const statusLabel = { created: "📝 создана", scheduled: "📅 назначена", in_progress: "🔧 в работе", completed: "✅ завершена", cancelled: "❌ отменена", }[a.status] || a.status; const card = el(`
${statusLabel} ${escHtml(dateStr)}
${escHtml(a.client_name || "Без имени")}
${escHtml(a.address || "адрес не указан")}
${a.scope_of_work ? `
${escHtml(a.scope_of_work.slice(0, 120))}${a.scope_of_work.length > 120 ? "…" : ""}
` : ""}
`); card.addEventListener("click", () => { haptic && haptic("impact"); location.hash = `#/assembly/${a.id}`; }); list.appendChild(card); } root.appendChild(list); } catch (e) { loading.remove(); root.appendChild(el(`
Сеть: ${escHtml(e.message)}
`)); } } /* ===================== Детальная карточка ===================== */ async function renderDetail(id) { if (!root) return; root.innerHTML = ""; root.appendChild(headerEl("Сборка", "")); const loading = el(`
`); root.appendChild(loading); let a; try { const res = await fetch(`${BACKEND_URL}/api/assembly_detail`, { method: "POST", body: JSON.stringify({ initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null, assembly_id: id, }), }); a = await res.json(); } catch (e) { loading.remove(); root.appendChild(el(`
Сеть: ${escHtml(e.message)}
`)); return; } loading.remove(); if (a.error) { root.appendChild(el(`
${escHtml(a.error)}
`)); return; } const dateStr = a.scheduled_at ? formatDateHuman(a.scheduled_at) : "Не назначена"; const statusLabel = { created: "📝 создана", scheduled: "📅 назначена", in_progress: "🔧 в работе", completed: "✅ завершена", cancelled: "❌ отменена", }[a.status] || a.status; root.appendChild(el(`
Сборка #${(a.id || "").slice(0, 8)} · ${statusLabel}

${escHtml(a.client_name || "Без имени")}

${a.client_phone ? `📞 ${escHtml(a.client_phone)}` : ""} 📍 ${escHtml(a.address || "адрес не указан")} 📅 ${escHtml(dateStr)}
`)); if (a.gcal_event_url) { root.appendChild(el(`
📅 Открыть в Google Calendar
`)); } root.appendChild(el(`
🛠 Состав работ
${escHtml(a.scope_of_work || "—")}
`)); if (a.manager_note) { root.appendChild(el(`
📝 Заметка от менеджера
${escHtml(a.manager_note)}
`)); } // Этапы 2-3 (фото / подпись) — добавим в следующем коммите root.appendChild(el(`
Фото-отчёт и приёмка появятся в следующем обновлении.
`)); } /* ===================== Helpers ===================== */ function escHtml(s) { return String(s == null ? "" : s) .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function escAttr(s) { return escHtml(s); } return { mount }; })();