This commit is contained in:
215
web/static/car_profile.js
Normal file
215
web/static/car_profile.js
Normal file
@@ -0,0 +1,215 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user