const tg = window.Telegram?.WebApp; tg?.ready(); tg?.expand(); const CarPassPage = (() => { const state = { user: null, authConfig: null }; function authHeaders(extra = {}) { const headers = { ...extra }; if (tg?.initData) headers["X-Telegram-Init-Data"] = tg.initData; if (!tg?.initData && state.authConfig?.allow_dev_auth) { headers["X-Dev-Telegram-Id"] = localStorage.getItem("driversDevTelegramId") || "1"; } return headers; } async function api(path, options = {}) { const { headers: optionHeaders = {}, ...fetchOptions } = options; const headers = { "Content-Type": "application/json", ...authHeaders(optionHeaders) }; if (options.body instanceof FormData) delete headers["Content-Type"]; const response = await fetch(`/api${path}`, { ...fetchOptions, headers }); if (!response.ok) throw new Error(await response.text() || response.statusText); if (response.status === 204) return null; return response.json(); } async function loadAuthConfig() { state.authConfig = await api("/users/auth/config"); } function showAuthOverlay() { document.body.classList.add("auth-required"); const link = document.querySelector("#telegramLoginLink"); if (state.authConfig?.bot_username && link) { link.href = `https://t.me/${state.authConfig.bot_username}`; link.classList.remove("hidden"); } } async function ensureUser() { if (tg?.initData) { state.user = await api("/users/webapp-auth", { method: "POST", body: JSON.stringify({ init_data: tg.initData }), }); } else if (state.authConfig?.allow_dev_auth) { state.user = await api("/users/me"); } else { showAuthOverlay(); throw new Error("Требуется вход через Telegram"); } document.body.classList.remove("auth-required"); document.querySelector("#authOverlay")?.classList.add("hidden"); return state.user; } function toast(message, tone = "success") { const node = document.querySelector("#toast"); if (!node) return; node.textContent = message; node.className = `toast ${tone}`; window.clearTimeout(toast.timer); toast.timer = window.setTimeout(() => node.classList.add("hidden"), 2600); } function setBusy(button, busy, label = "Сохраняю...") { if (!button) return; if (busy) { button.dataset.label = button.textContent; button.disabled = true; button.classList.add("is-busy"); button.innerHTML = `${label}`; } else { button.disabled = false; button.classList.remove("is-busy"); button.textContent = button.dataset.label || button.textContent; delete button.dataset.label; } } async function runAction(button, label, callback) { setBusy(button, true, label); try { const result = await callback(); return result; } catch (error) { toast(error.message || "Ошибка", "error"); throw error; } finally { setBusy(button, false, label); } } function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function formData(form) { return Object.fromEntries(new FormData(form).entries()); } function numberOrNull(value) { return value === "" || value == null ? null : Number(value); } function csvList(value) { return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : null; } function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value).slice(0, 16).replace("T", " "); return date.toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }); } function today() { return new Date().toISOString().slice(0, 10); } async function boot(init) { try { await loadAuthConfig(); await ensureUser(); await init(); } catch (error) { if (error.message === "Требуется вход через Telegram") return; console.error(error); toast(error.message || "Ошибка", "error"); } } document.querySelector("#telegramRetryBtn")?.addEventListener("click", () => window.location.reload()); return { state, api, boot, toast, runAction, escapeHtml, formData, numberOrNull, csvList, formatDateTime, today, }; })();