216 lines
8.5 KiB
JavaScript
216 lines
8.5 KiB
JavaScript
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();
|
||
});
|