/* ============================================================
AssemblerDashboard — личная аналитика сборщика
#/master/dashboard
============================================================ */
const AssemblerDashboard = (function () {
"use strict";
function escHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
function el(html) {
const t = document.createElement("template");
t.innerHTML = html.trim();
return t.content.firstChild;
}
function fmtMoney(n) {
return Math.round(n || 0).toLocaleString("ru-RU") + " ₽";
}
function fmtMonth(ym) {
try {
const d = new Date(ym + "-01");
return d.toLocaleDateString("ru-RU", { month: "long", year: "numeric" });
} catch { return ym; }
}
async function _api(path, body = {}) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 30000);
try {
const res = await fetch(`${BACKEND_URL}/api/${path}`, {
method: "POST", signal: ctrl.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
initData: (typeof Platform !== "undefined" ? Platform.initData : (window.tg?.initData || "")),
initDataUnsafe: (typeof Platform !== "undefined" ? Platform.initDataUnsafe : null),
...body,
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
if (e.name === "AbortError") throw new Error("Таймаут — попробуй ещё раз");
throw e;
} finally { clearTimeout(t); }
}
/* ── Главный экран ─────────────────────────────────────────── */
async function mount(container) {
container.innerHTML = "";
document.body.classList.remove("has-bottom-nav");
document.getElementById("bottom-nav")?.remove();
const h = el(`
`);
h.querySelector(".podbor-back").addEventListener("click", () => {
haptic && haptic("impact");
history.back();
});
const yearEl = el(`
`);
const screen = el(``);
container.appendChild(h);
container.appendChild(yearEl);
container.appendChild(screen);
const load = async (year) => {
screen.innerHTML = ``;
try {
const data = await _api("assembler_earnings", { year });
if (data.error) {
screen.innerHTML = `${escHtml(data.error === "no_name" ? "Имя не задано в профиле. Обратитесь к менеджеру." : data.error)}
`;
return;
}
_render(screen, data);
} catch (e) {
screen.innerHTML = `Ошибка: ${escHtml(e.message)}
`;
}
};
yearEl.querySelector("#yearSelect").addEventListener("change", function () {
load(this.value);
});
h.querySelector("#reloadBtn").addEventListener("click", () => {
haptic && haptic("impact");
load(yearEl.querySelector("#yearSelect").value);
});
load("2026");
}
/* ── Рендер ─────────────────────────────────────────────────── */
function _render(screen, data) {
screen.innerHTML = "";
// Имя не нашли
if (!data.matched_name) {
screen.appendChild(el(`
🔍
Данные не найдены
${escHtml(data.message || "Ваше имя не найдено в таблице занятости")}
Имя в профиле: ${escHtml(data.full_name || "—")}
`));
return;
}
const months = data.months || {};
const monthKeys = Object.keys(months).sort().reverse();
// Текущий и прошлый месяц
const now = new Date();
const curYM = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
const prevYM = `${prevDate.getFullYear()}-${String(prevDate.getMonth() + 1).padStart(2, "0")}`;
const curMonth = months[curYM] || null;
const prevMonth = months[prevYM] || null;
// === Hero-карточка ===
const heroCard = el(`
Всего за период
${escHtml(fmtMoney(data.total_amount))}
${escHtml(String(data.total_orders))} заказов
${data.match_score < 2 ? `
⚠ Неточное совпадение: «${escHtml(data.matched_name)}»
` : ""}
`);
screen.appendChild(heroCard);
// === Мини-карточки текущий / прошлый месяц ===
if (curMonth || prevMonth) {
const row = el(``);
const _miniCard = (label, m) => {
if (!m) return el(``);
return el(`
${escHtml(label)}
${escHtml(fmtMoney(m.total_amount))}
${escHtml(String(m.orders))} заказов
`);
};
row.appendChild(_miniCard("Текущий месяц", curMonth));
row.appendChild(_miniCard("Прошлый месяц", prevMonth));
screen.appendChild(row);
}
// === Таблица по месяцам ===
if (monthKeys.length) {
screen.appendChild(el(`📅 По месяцам
`));
const maxAmt = Math.max(...monthKeys.map(k => months[k].total_amount)) || 1;
monthKeys.forEach(ym => {
const m = months[ym];
const pct = Math.round((m.total_amount / maxAmt) * 100);
const avgPer = m.orders ? Math.round(m.total_amount / m.orders) : 0;
const isCurrentMonth = ym === curYM;
const card = el(`
${escHtml(fmtMonth(ym))}
${isCurrentMonth ? `сейчас` : ""}
${escHtml(fmtMoney(m.total_amount))}
${escHtml(String(m.orders))} зак. · ср. ${escHtml(fmtMoney(avgPer))}
`);
screen.appendChild(card);
});
} else {
screen.appendChild(el(`
Нет данных за выбранный период.
Попробуй выбрать другой год.
`));
}
// Footer
if (data.parsed_at) {
const parsedAt = new Date(data.parsed_at).toLocaleString("ru-RU");
screen.appendChild(el(`
Данные обновлены: ${escHtml(parsedAt)}
`));
}
screen.appendChild(el(``));
}
return { mount };
})();