Files
drivers_bot/web/static/book_sto.js
VPN SaaS Dev 58ff6ff614
Some checks failed
ci / test (push) Has been cancelled
compact UI and add localization switch
2026-05-19 05:05:24 +09:00

161 lines
7.1 KiB
JavaScript
Raw 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 params = new URLSearchParams(window.location.search);
const state = {
centers: [],
vehicles: [],
selectedCenterId: Number(params.get("service_center_id") || 0) || null,
};
const SERVICE_NAMES = {
oil_change: "Замена масла",
diagnostics: "Диагностика",
maintenance: "Техническое обслуживание",
tire_service: "Шиномонтаж",
brakes: "Тормозная система",
repair: "Ремонт",
other: "Другое",
};
function selectedCenter() {
return state.centers.find((item) => item.id === state.selectedCenterId) || null;
}
function renderCenters() {
const root = document.querySelector("#serviceList");
root.innerHTML = state.centers.length
? state.centers.map((center) => `
<button type="button" class="service-list-card ${center.id === state.selectedCenterId ? "active" : ""}" data-center="${center.id}">
<strong>${page.escapeHtml(center.display_name || center.name)}</strong>
<small>${page.escapeHtml([center.city, center.address].filter(Boolean).join(", ") || "Адрес уточняется")}</small>
<small>${center.nearest_slot_at ? `Ближайшее окно: ${page.formatDateTime(center.nearest_slot_at)}` : "Онлайн-запись по графику СТО"}</small>
</button>
`).join("")
: `<div class="empty">Подходящих СТО не найдено.</div>`;
root.querySelectorAll("[data-center]").forEach((button) => {
button.addEventListener("click", async () => {
state.selectedCenterId = Number(button.dataset.center);
renderCenters();
renderBookingHead();
await loadSlots();
});
});
}
function renderBookingHead() {
const center = selectedCenter();
document.querySelector("#bookingTitle").textContent = center ? (center.display_name || center.name) : "Выберите сервис";
document.querySelector("#bookingHint").textContent = center
? [center.city, center.address, center.working_hours].filter(Boolean).join(" · ") || "Выберите удобное время записи."
: "Выберите СТО слева, потом автомобиль, услугу и свободное окно.";
}
function renderVehicles() {
const select = document.querySelector("#vehicleSelect");
select.innerHTML = state.vehicles.length
? state.vehicles.map((car) => `<option value="${car.id}">${page.escapeHtml([car.name, car.make, car.model, car.license_plate_display].filter(Boolean).join(" · "))}</option>`).join("")
: `<option value="">Сначала добавьте автомобиль</option>`;
select.disabled = !state.vehicles.length;
}
async function loadCenters(filters = {}) {
const query = new URLSearchParams();
query.set("has_slots", "true");
if (filters.city) query.set("city", filters.city);
if (filters.specialization) query.set("specialization", filters.specialization);
state.centers = await page.api(`/sto/catalog?${query.toString()}`);
if (state.selectedCenterId && !state.centers.some((item) => item.id === state.selectedCenterId)) {
state.centers = [await page.api(`/service-centers/${state.selectedCenterId}`).catch(() => null), ...state.centers].filter(Boolean);
}
if (!state.selectedCenterId && state.centers.length) state.selectedCenterId = state.centers[0].id;
renderCenters();
renderBookingHead();
}
async function loadVehicles() {
state.vehicles = await page.api("/my/vehicles");
renderVehicles();
}
async function loadSlots() {
const center = selectedCenter();
const select = document.querySelector("#slotSelect");
if (!center) {
select.innerHTML = `<option value="">Выберите СТО</option>`;
select.disabled = true;
return;
}
const form = document.querySelector("#bookingForm");
const data = page.formData(form);
const date = data.date || page.today();
const serviceType = data.service_type || "maintenance";
const duration = data.estimated_duration_minutes || "60";
document.querySelector("#slotHint").textContent = "Проверяю свободные окна...";
const slots = await page.api(`/sto/${center.id}/available-slots?service_type=${encodeURIComponent(serviceType)}&date_from=${date}&date_to=${date}&duration_minutes=${duration}`);
select.disabled = !slots.length;
select.innerHTML = slots.length
? slots.map((slot) => `<option value="${slot.start_at}">${page.formatDateTime(slot.start_at)} - ${page.formatDateTime(slot.end_at).slice(-5)}</option>`).join("")
: `<option value="">На эту дату окон нет</option>`;
document.querySelector("#slotHint").textContent = slots.length ? "Выберите удобное окно." : "Попробуйте другую дату или длительность.";
}
document.querySelector("#filterForm").addEventListener("submit", async (event) => {
event.preventDefault();
await page.runAction(event.currentTarget.querySelector("button"), "Ищу СТО...", async () => {
await loadCenters(page.formData(event.currentTarget));
await loadSlots();
});
});
["#serviceTypeSelect", "#durationSelect", "#bookingDateInput"].forEach((selector) => {
document.querySelector(selector).addEventListener("change", () => {
loadSlots().catch((error) => page.toast(error.message || "Не удалось обновить окна", "error"));
});
});
document.querySelectorAll("[data-booking-service]").forEach((button) => {
button.addEventListener("click", async () => {
document.querySelector("#serviceTypeSelect").value = button.dataset.bookingService;
document.querySelectorAll("[data-booking-service]").forEach((item) => {
item.classList.toggle("active", item === button);
});
await loadSlots().catch((error) => page.toast(error.message || "Не удалось обновить окна", "error"));
});
});
document.querySelector("#bookingForm").addEventListener("submit", async (event) => {
event.preventDefault();
const center = selectedCenter();
const data = page.formData(event.currentTarget);
if (!center) {
page.toast("Выберите СТО", "error");
return;
}
if (!data.vehicle_id) {
page.toast("Добавьте автомобиль перед записью", "error");
return;
}
await page.runAction(document.querySelector("#createBookingBtn"), "Отправляю заявку...", async () => {
await page.api("/appointments", {
method: "POST",
body: JSON.stringify({
service_center_id: center.id,
vehicle_id: Number(data.vehicle_id),
service_type: data.service_type,
service_name: SERVICE_NAMES[data.service_type] || "Обслуживание",
requested_start_at: data.slot,
estimated_duration_minutes: Number(data.estimated_duration_minutes || 60),
customer_comment: data.customer_comment || null,
}),
});
page.toast("Заявка отправлена в СТО");
window.setTimeout(() => { window.location.href = "/?section=appointments"; }, 700);
});
});
page.boot(async () => {
document.querySelector("#bookingDateInput").value = page.today();
await Promise.all([loadCenters(), loadVehicles()]);
await loadSlots();
});