// 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 };
})();