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