Files
drivers_bot/web/static/car_profile.js
VPN SaaS Dev ecfb5aa949
Some checks failed
ci / test (push) Has been cancelled
Refactor menu flows into dedicated pages
2026-05-16 11:59:09 +09:00

216 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const page = CarPassPage;
const state = {
cars: [],
catalog: [],
selectedCarId: null,
};
const params = new URLSearchParams(window.location.search);
function selectedCar() {
return state.cars.find((item) => item.id === state.selectedCarId) || null;
}
function ensureOption(select, value) {
if (!value) return;
if (![...select.options].some((option) => option.value === value)) {
select.insertAdjacentHTML("beforeend", `<option value="${page.escapeHtml(value)}">${page.escapeHtml(value)}</option>`);
}
}
function selectedModel() {
const makeName = document.querySelector("#makeSelect").value;
const modelName = document.querySelector("#modelSelect").value;
const make = state.catalog.find((item) => item.name === makeName);
return make?.models?.find((item) => item.name === modelName) || null;
}
function syncModels(modelValue = "", trimValue = "") {
const makeName = document.querySelector("#makeSelect").value;
const modelSelect = document.querySelector("#modelSelect");
const models = state.catalog.find((item) => item.name === makeName)?.models || [];
modelSelect.disabled = !models.length;
modelSelect.innerHTML = models.length
? `<option value="">Модель</option>` + models.map((model) => `<option value="${page.escapeHtml(model.name)}">${page.escapeHtml(model.name)}</option>`).join("")
: `<option value="">Сначала марка</option>`;
ensureOption(modelSelect, modelValue);
modelSelect.value = modelValue || "";
syncTrims(trimValue);
}
function syncTrims(trimValue = "") {
const trimSelect = document.querySelector("#trimSelect");
const trims = selectedModel()?.trims || [];
trimSelect.disabled = !trims.length;
trimSelect.innerHTML = trims.length
? `<option value="">Комплектация</option>` + trims.map((trim) => `<option value="${page.escapeHtml(trim.name)}">${page.escapeHtml(trim.name)}</option>`).join("")
: `<option value="">Сначала модель</option>`;
ensureOption(trimSelect, trimValue);
trimSelect.value = trimValue || "";
const trim = trims.find((item) => item.name === trimSelect.value);
const fuel = document.querySelector('[name="fuel_type"]');
if (trim?.fuel_type && !fuel.value) fuel.value = trim.fuel_type;
if (trim?.body_type && !document.querySelector('[name="body_type"]')?.value) {
const bodyInput = document.querySelector('[name="body_type"]');
if (bodyInput) bodyInput.value = trim.body_type;
}
}
async function loadCatalog() {
state.catalog = await page.api("/catalog/makes");
const makeSelect = document.querySelector("#makeSelect");
const makes = [...state.catalog].sort((a, b) => a.name.localeCompare(b.name, "ru"));
makeSelect.innerHTML = `<option value="">Марка</option>` + makes
.map((make) => `<option value="${page.escapeHtml(make.name)}">${page.escapeHtml(make.name)}</option>`)
.join("");
makeSelect.addEventListener("change", () => syncModels());
document.querySelector("#modelSelect").addEventListener("change", () => syncTrims());
document.querySelector("#trimSelect").addEventListener("change", () => syncTrims(document.querySelector("#trimSelect").value));
}
async function loadCars() {
state.cars = await page.api(`/cars?owner_id=${page.state.user.id}`);
const routeCarId = Number(params.get("car_id") || 0);
if (params.get("action") === "new") state.selectedCarId = null;
else if (routeCarId && state.cars.some((item) => item.id === routeCarId)) state.selectedCarId = routeCarId;
else if (!state.selectedCarId && state.cars.length) state.selectedCarId = state.cars[0].id;
renderVehicles();
fillForm();
}
function renderVehicles() {
const root = document.querySelector("#vehicleList");
root.innerHTML = state.cars.length
? state.cars.map((car) => `
<button type="button" class="service-list-card ${car.id === state.selectedCarId ? "active" : ""}" data-vehicle="${car.id}">
<strong>${page.escapeHtml(car.name)}</strong>
<small>${page.escapeHtml([car.make, car.model, car.year, car.license_plate_display].filter(Boolean).join(" · ") || "Паспорт без деталей")}</small>
</button>
`).join("")
: `<div class="empty">Автомобилей пока нет. Заполните форму справа.</div>`;
root.querySelectorAll("[data-vehicle]").forEach((button) => {
button.addEventListener("click", () => {
state.selectedCarId = Number(button.dataset.vehicle);
renderVehicles();
fillForm();
});
});
}
function setValue(form, name, value) {
const input = form.elements[name];
if (!input) return;
input.value = value ?? "";
}
function fillForm() {
const form = document.querySelector("#vehicleProfileForm");
const car = selectedCar();
form.reset();
document.querySelector("#deleteVehicleBtn").classList.toggle("hidden", !car);
document.querySelector("#pageTitle").textContent = car ? car.name : "Новый автомобиль";
document.querySelector("#pageHint").textContent = car
? [car.make, car.model, car.year, car.license_plate_display].filter(Boolean).join(" · ") || "Заполните недостающие данные паспорта."
: "Создайте карточку, а потом дополняйте ее по мере обслуживания.";
if (!car) {
syncModels();
return;
}
[
"name",
"year",
"plate_number",
"vin",
"current_odometer",
"fuel_type",
"engine_volume_l",
"transmission",
"drive_type",
"engine_oil_type",
"engine_oil_volume_l",
"transmission_fluid_type",
"transmission_fluid_volume_l",
"coolant_type",
"brake_fluid_type",
"tire_size",
"oil_change_interval_km",
"purchase_price",
"purchase_date",
"purchase_type",
"notes",
].forEach((name) => setValue(form, name, car[name]));
ensureOption(form.elements.make, car.make);
form.elements.make.value = car.make || "";
syncModels(car.model || "", car.trim || "");
}
function payloadFromForm(form) {
const data = page.formData(form);
return {
name: data.name,
make: data.make || null,
model: data.model || null,
trim: data.trim || null,
year: page.numberOrNull(data.year),
plate_number: data.plate_number || null,
vin: data.vin || null,
current_odometer: page.numberOrNull(data.current_odometer),
fuel_type: data.fuel_type || null,
engine_volume_l: page.numberOrNull(data.engine_volume_l),
transmission: data.transmission || null,
drive_type: data.drive_type || null,
engine_oil_type: data.engine_oil_type || null,
engine_oil_volume_l: page.numberOrNull(data.engine_oil_volume_l),
transmission_fluid_type: data.transmission_fluid_type || null,
transmission_fluid_volume_l: page.numberOrNull(data.transmission_fluid_volume_l),
coolant_type: data.coolant_type || null,
brake_fluid_type: data.brake_fluid_type || null,
tire_size: data.tire_size || null,
oil_change_interval_km: page.numberOrNull(data.oil_change_interval_km),
purchase_price: page.numberOrNull(data.purchase_price),
purchase_date: data.purchase_date || null,
purchase_type: data.purchase_type || "unknown",
purchase_currency: page.state.user?.currency || "RUB",
currency: page.state.user?.currency || "RUB",
notes: data.notes || null,
};
}
document.querySelector("#newVehicleBtn").addEventListener("click", () => {
state.selectedCarId = null;
renderVehicles();
fillForm();
});
document.querySelector("#vehicleProfileForm").addEventListener("submit", async (event) => {
event.preventDefault();
const form = event.currentTarget;
const car = selectedCar();
await page.runAction(document.querySelector("#saveVehicleBtn"), "Сохраняю паспорт...", async () => {
const saved = await page.api(car ? `/cars/${car.id}` : "/cars", {
method: car ? "PATCH" : "POST",
body: JSON.stringify(payloadFromForm(form)),
});
state.selectedCarId = saved.id;
await loadCars();
page.toast("Паспорт сохранен");
});
});
document.querySelector("#deleteVehicleBtn").addEventListener("click", async (event) => {
const car = selectedCar();
if (!car || !window.confirm(`Удалить автомобиль «${car.name}» и все его записи?`)) return;
await page.runAction(event.currentTarget, "Удаляю автомобиль...", async () => {
await page.api(`/cars/${car.id}`, { method: "DELETE" });
state.selectedCarId = null;
await loadCars();
page.toast("Автомобиль удален");
});
});
page.boot(async () => {
await loadCatalog();
await loadCars();
});