/* ============================================================
MeasurerDashboard — личная статистика замерщика
#/master/measurer-stats
============================================================ */
const MeasurerDashboard = (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("measurer_earnings", { year });
if (data.error) {
screen.innerHTML = `${escHtml(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 = "";
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 prevD = new Date(now.getFullYear(), now.getMonth() - 1, 1);
const prevYM = `${prevD.getFullYear()}-${String(prevD.getMonth() + 1).padStart(2, "0")}`;
const curMonth = months[curYM] || null;
const prevMonth = months[prevYM] || null;
// Hero
screen.appendChild(el(`
Всего за период
${escHtml(fmtMoney(data.total_amount))}
${escHtml(String(data.total_measurements))} замеров
${data.total_amount > 0 ? ` · ${escHtml(fmtMoney(data.total_amount / data.total_measurements))} в среднем` : ""}
`));
// Мини-карточки
if (curMonth || prevMonth) {
const row = el(``);
const mini = (label, m) => !m
? el(``)
: el(`
${escHtml(label)}
${escHtml(fmtMoney(m.total_amount))}
${m.measurements} замеров · ${m.paid} оплачено
`);
row.appendChild(mini("Текущий месяц", curMonth));
row.appendChild(mini("Прошлый месяц", prevMonth));
screen.appendChild(row);
}
if (!monthKeys.length) {
screen.appendChild(el(`
📐
Замеров за этот период нет
Данные появятся после выставления счёта за замер
`));
return;
}
// Таблица по месяцам
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 isCur = ym === curYM;
screen.appendChild(el(`
${escHtml(fmtMonth(ym))}
${isCur ? `сейчас` : ""}
${m.total_amount > 0 ? escHtml(fmtMoney(m.total_amount)) : "—"}
${m.measurements} замеров · ${m.paid} со счётом
${m.total_amount > 0 ? `
` : ""}
`));
});
screen.appendChild(el(`
💡 Сумма учитывается когда вы выставляете счёт за замер через кнопку «💳 Выставить счёт» в карточке клиента
`));
screen.appendChild(el(``));
}
return { mount };
})();