This commit is contained in:
@@ -4,6 +4,8 @@ tg?.expand();
|
||||
|
||||
const textNodes = new WeakMap();
|
||||
const attrOriginals = new WeakMap();
|
||||
let translationObserver = null;
|
||||
let translationTimer = null;
|
||||
|
||||
const i18n = {
|
||||
en: {
|
||||
@@ -83,6 +85,14 @@ const i18n = {
|
||||
"Марка": "Make",
|
||||
"Модель": "Model",
|
||||
"Добавить авто": "Add vehicle",
|
||||
"Добавить запись": "Add entry",
|
||||
"Расход": "Expense",
|
||||
"дата, пробег, литры, цена": "date, odometer, liters, price",
|
||||
"работа, стоимость, следующий срок": "work, cost, next due",
|
||||
"страховка, штраф, парковка, прочее": "insurance, fine, parking, other",
|
||||
"фото или файл": "photo or file",
|
||||
"Дополнительно": "More options",
|
||||
"Напоминание о следующем ТО": "Next maintenance reminder",
|
||||
"За весь срок": "All time",
|
||||
"За месяц": "This month",
|
||||
"За день": "Per day",
|
||||
@@ -215,6 +225,14 @@ const i18n = {
|
||||
"Марка": "브랜드",
|
||||
"Модель": "모델",
|
||||
"Добавить авто": "차량 추가",
|
||||
"Добавить запись": "기록 추가",
|
||||
"Расход": "지출",
|
||||
"дата, пробег, литры, цена": "날짜, 주행거리, 리터, 가격",
|
||||
"работа, стоимость, следующий срок": "작업, 비용, 다음 예정",
|
||||
"страховка, штраф, парковка, прочее": "보험, 벌금, 주차, 기타",
|
||||
"фото или файл": "사진 또는 파일",
|
||||
"Дополнительно": "추가 옵션",
|
||||
"Напоминание о следующем ТО": "다음 정비 알림",
|
||||
"За весь срок": "전체",
|
||||
"За месяц": "월",
|
||||
"За день": "일 평균",
|
||||
@@ -304,6 +322,15 @@ function applyTranslations(root = document.body) {
|
||||
});
|
||||
}
|
||||
|
||||
function observeTranslations(root = document.body) {
|
||||
if (translationObserver || !root) return;
|
||||
translationObserver = new MutationObserver(() => {
|
||||
window.clearTimeout(translationTimer);
|
||||
translationTimer = window.setTimeout(() => applyTranslations(root), 40);
|
||||
});
|
||||
translationObserver.observe(root, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
|
||||
const state = {
|
||||
user: null,
|
||||
@@ -582,6 +609,7 @@ async function ensureUser() {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ init_data: tg.initData }),
|
||||
});
|
||||
localStorage.setItem("carpassLocale", state.user.locale || "ru");
|
||||
hideAuthOverlay();
|
||||
updateRoleVisibility();
|
||||
return;
|
||||
@@ -590,6 +618,7 @@ async function ensureUser() {
|
||||
const devId = localStorage.getItem("driversDevTelegramId") || "1";
|
||||
localStorage.setItem("driversDevTelegramId", devId);
|
||||
state.user = await api("/users/me");
|
||||
localStorage.setItem("carpassLocale", state.user.locale || "ru");
|
||||
hideAuthOverlay();
|
||||
updateRoleVisibility();
|
||||
return;
|
||||
@@ -598,6 +627,36 @@ async function ensureUser() {
|
||||
throw new Error("Требуется вход через Telegram");
|
||||
}
|
||||
|
||||
function installLocaleSwitch() {
|
||||
const topActions = document.querySelector(".topbar .top-actions");
|
||||
if (!topActions || document.querySelector("#globalLocaleSelect")) return;
|
||||
const select = document.createElement("select");
|
||||
select.id = "globalLocaleSelect";
|
||||
select.className = "locale-switch";
|
||||
select.setAttribute("aria-label", "Язык");
|
||||
select.innerHTML = `
|
||||
<option value="ru">RU</option>
|
||||
<option value="en">EN</option>
|
||||
<option value="ko">KO</option>
|
||||
`;
|
||||
select.value = state.user?.locale || "ru";
|
||||
select.addEventListener("change", async () => {
|
||||
await runAction(select, "Сохраняю...", async () => {
|
||||
state.user = await api(`/users/${state.user.id}/preferences`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ locale: select.value, currency: state.user.currency }),
|
||||
});
|
||||
localStorage.setItem("carpassLocale", state.user.locale || "ru");
|
||||
document.querySelector("#localeSelect").value = state.user.locale || "ru";
|
||||
applyTranslations();
|
||||
renderCars();
|
||||
renderStats(state.latestStats);
|
||||
toast("Сохранено");
|
||||
});
|
||||
});
|
||||
topActions.prepend(select);
|
||||
}
|
||||
|
||||
function hideAuthOverlay() {
|
||||
document.querySelector("#authOverlay")?.classList.add("hidden");
|
||||
document.body.classList.remove("auth-required");
|
||||
@@ -2671,6 +2730,9 @@ document.querySelector("#settingsForm").addEventListener("submit", async (event)
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ locale: data.locale, currency: data.currency }),
|
||||
});
|
||||
localStorage.setItem("carpassLocale", state.user.locale || "ru");
|
||||
const globalLocale = document.querySelector("#globalLocaleSelect");
|
||||
if (globalLocale) globalLocale.value = state.user.locale || "ru";
|
||||
applyTranslations();
|
||||
initCarCatalog();
|
||||
await loadSelectedCar();
|
||||
@@ -2700,6 +2762,7 @@ document.querySelector("#fuelForm").addEventListener("submit", async (event) =>
|
||||
});
|
||||
form.reset();
|
||||
form.entry_date.value = today();
|
||||
form.is_full_tank.checked = true;
|
||||
await loadSelectedCar();
|
||||
toast("Сохранено");
|
||||
haptic("success");
|
||||
@@ -2802,6 +2865,20 @@ function mountEntryForms() {
|
||||
}
|
||||
}
|
||||
|
||||
function fillEntryDefaults(sectionId) {
|
||||
const car = selectedCar();
|
||||
const odometer = car?.current_odometer || "";
|
||||
const sections = sectionId ? [document.querySelector(`#${sectionId}`)] : [...document.querySelectorAll(".drawer-section")];
|
||||
sections.filter(Boolean).forEach((section) => {
|
||||
section.querySelectorAll('input[name="entry_date"]').forEach((input) => {
|
||||
if (!input.value) input.value = today();
|
||||
});
|
||||
section.querySelectorAll('input[name="odometer"]').forEach((input) => {
|
||||
if (!input.value && odometer) input.value = odometer;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function openDrawerSection(sectionId, options = {}) {
|
||||
if (!canOpenDrawerSection(sectionId)) {
|
||||
toast("Этот раздел недоступен для вашей роли", "error");
|
||||
@@ -2817,6 +2894,7 @@ async function openDrawerSection(sectionId, options = {}) {
|
||||
button.classList.toggle("active", button.dataset.menuSection === sectionId);
|
||||
});
|
||||
mountEntryForms();
|
||||
fillEntryDefaults(sectionId);
|
||||
if (sectionId === "carProfileSection") fillCarProfileForm();
|
||||
if (sectionId === "settingsSection") {
|
||||
document.querySelector("#localeSelect").value = state.user?.locale || "ru";
|
||||
@@ -2891,7 +2969,18 @@ document.querySelector("#addCarQuickBtn").addEventListener("click", () => {
|
||||
});
|
||||
|
||||
document.querySelector("#addRecordPrimaryBtn").addEventListener("click", () => {
|
||||
openDrawerSection("expensesSection");
|
||||
openDrawerSection("quickAddSection");
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-quick-entry]").forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
haptic();
|
||||
if (button.dataset.quickEntry === "scan") {
|
||||
openScanModal();
|
||||
return;
|
||||
}
|
||||
await openDrawerSection(button.dataset.quickEntry);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-menu-section]").forEach((button) => {
|
||||
@@ -3043,6 +3132,8 @@ initPwa();
|
||||
Promise.all([loadAuthConfig()])
|
||||
.then(() => Promise.all([ensureUser(), loadCatalog()]))
|
||||
.then(() => {
|
||||
installLocaleSwitch();
|
||||
observeTranslations();
|
||||
document.querySelector("#localeSelect").value = state.user?.locale || "ru";
|
||||
document.querySelector("#currencySelect").value = state.user?.currency || "RUB";
|
||||
document.querySelector("#expenseForm").currency.value = state.user?.currency || "RUB";
|
||||
|
||||
Reference in New Issue
Block a user