// dispatcher_dashboard.js v=20260521a const DispatcherDashboard = (function () { "use strict"; const STATUS_LABEL = { created: "Новая", shipped: "В пути", arrived: "На складе", scheduled: "Назначена", in_progress: "В работе", completed: "Завершена", }; const STATUS_COLOR = { created: "#FFF3CD", shipped: "#CCE5FF", arrived: "#D4EDDA", scheduled: "#E2D9F3", in_progress: "#FFE0B2", completed: "#E8F5E9", }; const STATUS_TEXT_COLOR = { created: "#856404", shipped: "#004085", arrived: "#155724", scheduled: "#4A235A", in_progress: "#E65100", completed: "#1B5E20", }; function fmt(iso) { if (!iso) return "—"; const d = iso.slice(0, 10).split("-"); return d[2] + "." + d[1] + "." + d[0]; } function el(html) { const t = document.createElement("div"); t.innerHTML = html.trim(); return t.firstChild; } function badge(status) { return `${STATUS_LABEL[status] || status}`; } function showError(container, msg) { container.innerHTML = `
${msg}
`; } // ─── MAIN LIST ──────────────────────────────────────────────── async function mount(container) { container.innerHTML = `
📦 Диспетчер
`; const body = container.querySelector("#disp-body"); body.innerHTML = `
Загрузка…
`; let data; try { data = await _api("/api/dispatcher_inbox", {}); } catch (e) { showError(body, "Ошибка сети: " + e.message); return; } if (data.error) { showError(body, data.error); return; } const items = data.assemblies || []; if (!items.length) { body.innerHTML = `
Нет сборок
`; return; } // Group by status const GROUPS = ["created","shipped","arrived","scheduled","in_progress","completed"]; const grouped = {}; GROUPS.forEach(s => { grouped[s] = []; }); items.forEach(a => { (grouped[a.status] || (grouped["created"])).push(a); }); body.innerHTML = ""; GROUPS.forEach(status => { const list = grouped[status]; if (!list.length) return; const section = el(`
${STATUS_LABEL[status]} (${list.length})
`); list.forEach(a => section.appendChild(_card(a, container))); body.appendChild(section); }); } function _card(a, container) { const card = el(`
${a.client_name || "—"} ${badge(a.status)}
📍 ${a.address || "—"}
${a.shipment_date ? `
🚚 Отгружено: ${fmt(a.shipment_date)} · ${a.packages_count || "?"} уп.
` : ""} ${a.arrival_date ? `
🏭 Принято: ${fmt(a.arrival_date)} · ${a.arrival_packages_count || "?"} уп.
` : ""} ${a.scheduled_at ? `
📅 Назначено: ${fmt(a.scheduled_at)}
` : ""}
`); card.addEventListener("click", () => mountDetail(container, a)); return card; } // ─── DETAIL / EDIT ──────────────────────────────────────────── async function mountDetail(container, a) { container.innerHTML = `
${a.client_name} ${badge(a.status)}
`; container.querySelector("#back-btn").addEventListener("click", () => mount(container)); const body = container.querySelector("#detail-body"); // Info block body.appendChild(el(`
📍 ${a.address || "—"}
${a.scope_of_work ? `
📝 ${a.scope_of_work}
` : ""} ${a.client_phone ? `
📞 ${a.client_phone}
` : ""} ${a.manager_note ? `
💬 ${a.manager_note}
` : ""}
`)); // Step 1: Shipment body.appendChild(_stepShipment(a, container)); // Step 2: Arrival body.appendChild(_stepArrival(a, container)); // Step 3: Dispatch body.appendChild(_stepDispatch(a, container)); } // ─── STEP 1: ОТГРУЗКА ───────────────────────────────────────── function _stepShipment(a, container) { const done = !!a.shipment_date; const wrap = el(`
${done ? "✅" : "1️⃣"} Отгрузка с фабрики
${done ? `
Дата: ${fmt(a.shipment_date)} · Упаковок: ${a.packages_count || "—"}
` : ""}
`); if (!done) { const form = el(`
`); form.querySelector("#ship-save").addEventListener("click", async () => { const date = form.querySelector("#ship-date").value; const count = form.querySelector("#ship-count").value; if (!date) { alert("Укажите дату отгрузки"); return; } haptic && haptic("impact"); const btn = form.querySelector("#ship-save"); btn.disabled = true; btn.textContent = "Сохраняем…"; const res = await _api("/api/assembly_set_shipment", { assembly_id: a.id, shipment_date: date, packages_count: count }); if (res.ok) { a.shipment_date = date; a.packages_count = count; a.status = "shipped"; mountDetail(container, a); } else { alert(res.error || "Ошибка"); btn.disabled = false; btn.textContent = "Сохранить отгрузку"; } }); wrap.appendChild(form); } return wrap; } // ─── STEP 2: ПРИЁМКА НА СКЛАД ───────────────────────────────── function _stepArrival(a, container) { const done = !!a.arrival_date; const enabled = !!a.shipment_date; const wrap = el(`
${done ? "✅" : "2️⃣"} Приёмка на склад
${done ? `
Дата: ${fmt(a.arrival_date)} · Принято: ${a.arrival_packages_count || "—"} уп.
` : ""}
`); if (enabled && !done) { const form = el(`
`); form.querySelector("#arr-save").addEventListener("click", async () => { const date = form.querySelector("#arr-date").value; const count = form.querySelector("#arr-count").value; if (!date) { alert("Укажите дату приёмки"); return; } haptic && haptic("impact"); const btn = form.querySelector("#arr-save"); btn.disabled = true; btn.textContent = "Сохраняем…"; const res = await _api("/api/assembly_set_arrival", { assembly_id: a.id, arrival_date: date, arrival_packages_count: count }); if (res.ok) { a.arrival_date = date; a.arrival_packages_count = count; a.status = "arrived"; mountDetail(container, a); } else { alert(res.error || "Ошибка"); btn.disabled = false; btn.textContent = "Подтвердить приёмку"; } }); wrap.appendChild(form); } return wrap; } // ─── STEP 3: НАЗНАЧИТЬ ЭКСПЕДИТОРА И ДАТУ ───────────────────── function _stepDispatch(a, container) { const done = a.status === "scheduled" || a.status === "in_progress" || a.status === "completed"; const enabled = !!a.arrival_date; const wrap = el(`
${done ? "✅" : "3️⃣"} Назначить дату и экспедитора
${done ? `
Дата: ${fmt(a.scheduled_at)}${a.expeditor_tg_id ? " · Экспедитор: tg:" + a.expeditor_tg_id : ""}
` : ""}
`); if (enabled && !done) { const form = el(`
Загрузка…
`); // Load expeditors list _loadExpeditors(form.querySelector("#exp-picker")); form.querySelector("#disp-save").addEventListener("click", async () => { const date = form.querySelector("#disp-date").value; const sel = form.querySelector("#exp-select"); const expId = sel ? sel.value : ""; if (!date) { alert("Укажите дату сборки"); return; } haptic && haptic("impact"); const btn = form.querySelector("#disp-save"); btn.disabled = true; btn.textContent = "Назначаем…"; const res = await _api("/api/assembly_assign_dispatch", { assembly_id: a.id, scheduled_at: date, expeditor_tg_id: expId }); if (res.ok) { a.scheduled_at = date; a.expeditor_tg_id = expId; a.status = "scheduled"; mountDetail(container, a); } else { alert(res.error || "Ошибка"); btn.disabled = false; btn.textContent = "Назначить"; } }); wrap.appendChild(form); } return wrap; } async function _loadExpeditors(container) { try { const res = await _api("/api/staff_list", { role: "expeditor" }); const users = res.users || res.staff || []; if (!users.length) { container.innerHTML = `
Нет экспедиторов
`; return; } const opts = users.map(u => ``).join(""); container.innerHTML = ` `; } catch (e) { container.innerHTML = ` `; } } return { mount, mountDetail }; })();