Files
drivers_bot/web/static/sto_settings.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

180 lines
7.7 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 APPROVED_STATUSES = new Set(["approved", "verified"]);
const MANAGER_ROLES = new Set(["owner", "manager"]);
const state = {
centers: [],
activeCenterId: null,
catalog: null,
};
function activeCenter() {
return state.centers.find((item) => item.id === state.activeCenterId) || null;
}
function roleLabel(role) {
return { owner: "Владелец", manager: "Менеджер", receptionist: "Администратор", mechanic: "Механик" }[role] || role || "СТО";
}
function timeValue(value) {
return String(value || "").slice(0, 5);
}
function setScheduleForm(settings) {
const form = document.querySelector("#bookingSettingsForm");
form.open_time.value = timeValue(settings.open_time || "09:00");
form.close_time.value = timeValue(settings.close_time || "18:00");
form.lunch_break_start.value = timeValue(settings.lunch_break_start || "");
form.lunch_break_end.value = timeValue(settings.lunch_break_end || "");
form.slot_duration_minutes.value = settings.slot_duration_minutes ?? 30;
form.booking_buffer_minutes.value = settings.booking_buffer_minutes ?? 0;
form.max_parallel_bookings.value = settings.max_parallel_bookings ?? 1;
form.timezone.value = settings.timezone || "Asia/Seoul";
form.accepts_online_booking.checked = settings.accepts_online_booking !== false;
const days = new Set(settings.working_days || [0, 1, 2, 3, 4]);
form.querySelectorAll('[name="working_days"]').forEach((input) => {
input.checked = days.has(Number(input.value));
});
}
function schedulePayload(form, centerId) {
const data = page.formData(form);
const workingDays = [...form.querySelectorAll('[name="working_days"]:checked')].map((input) => Number(input.value));
return {
service_center_id: centerId,
working_days: workingDays,
open_time: data.open_time || "09:00",
close_time: data.close_time || "18:00",
lunch_break_start: data.lunch_break_start || null,
lunch_break_end: data.lunch_break_end || null,
timezone: data.timezone || "Asia/Seoul",
slot_duration_minutes: Number(data.slot_duration_minutes || 30),
booking_buffer_minutes: Number(data.booking_buffer_minutes || 0),
max_parallel_bookings: Number(data.max_parallel_bookings || 1),
accepts_online_booking: Boolean(data.accepts_online_booking),
};
}
function catalogPayload(form, centerId) {
const data = page.formData(form);
const isWork = data.item_type === "work";
return {
service_center_id: centerId,
item_type: data.item_type,
title: data.title,
category: data.category || null,
description: data.description || null,
work_type: isWork ? (data.category || "other") : null,
product_type: isWork ? null : (data.category || "other"),
brand: data.brand || null,
sku: data.sku || null,
unit: data.unit || (isWork ? "pcs" : "pcs"),
default_quantity: page.numberOrNull(data.default_quantity) || 1,
default_unit_price: page.numberOrNull(data.default_unit_price) || 0,
viscosity: data.viscosity || null,
specification: data.specification || null,
};
}
function renderHeader() {
const center = activeCenter();
document.querySelector("#centerSelect").innerHTML = state.centers
.map((item) => `<option value="${item.id}">${page.escapeHtml(item.display_name || item.name)}</option>`)
.join("");
if (center) document.querySelector("#centerSelect").value = String(center.id);
document.querySelector("#settingsTitle").textContent = center ? (center.display_name || center.name) : "Нет доступной СТО";
document.querySelector("#settingsHint").textContent = center
? [center.city, center.address].filter(Boolean).join(", ") || "Заполните график и каталог для команды."
: "Настройки доступны владельцу или менеджеру подтвержденной СТО.";
document.querySelector("#roleBadge").textContent = center ? roleLabel(center.employee_role) : "Нет доступа";
}
function renderCatalog() {
const root = document.querySelector("#catalogList");
const centerId = activeCenter()?.id;
const items = (state.catalog?.items || []).filter((item) => item.service_center_id === centerId);
root.innerHTML = items.length
? items.map((item) => `
<div class="stack-item">
<strong>${page.escapeHtml(item.title)}</strong>
<small>${page.escapeHtml([item.item_type === "work" ? "Работа" : "Материал", item.category, item.brand, item.sku].filter(Boolean).join(" · "))}</small>
<small>${page.escapeHtml(item.default_quantity)} ${page.escapeHtml(item.unit)} · ${page.escapeHtml(item.default_unit_price)}</small>
</div>
`).join("")
: `<div class="empty">Пока нет собственных позиций. Системный каталог уже доступен в заказ-нарядах.</div>`;
}
async function loadCenters() {
const centers = await page.api("/service-centers/my");
state.centers = centers.filter((center) =>
APPROVED_STATUSES.has(center.verification_status) && MANAGER_ROLES.has(center.employee_role || "owner"),
);
if (!state.activeCenterId && state.centers.length) state.activeCenterId = state.centers[0].id;
if (state.activeCenterId && !state.centers.some((item) => item.id === state.activeCenterId)) {
state.activeCenterId = state.centers[0]?.id || null;
}
renderHeader();
}
async function loadSettings() {
const center = activeCenter();
if (!center) {
document.querySelector("#bookingSettingsForm").classList.add("hidden");
document.querySelector("#catalogForm").classList.add("hidden");
document.querySelector("#catalogList").innerHTML = `<div class="empty">Нет подтвержденной СТО с ролью владельца или менеджера.</div>`;
return;
}
document.querySelector("#bookingSettingsForm").classList.remove("hidden");
document.querySelector("#catalogForm").classList.remove("hidden");
const [settings, catalog] = await Promise.all([
page.api(`/sto/settings/booking?service_center_id=${center.id}`),
page.api(`/work-orders/catalog?service_center_id=${center.id}`),
]);
state.catalog = catalog;
setScheduleForm(settings);
renderCatalog();
}
document.querySelector("#centerSelect").addEventListener("change", async (event) => {
state.activeCenterId = Number(event.currentTarget.value);
renderHeader();
await loadSettings();
});
document.querySelector("#bookingSettingsForm").addEventListener("submit", async (event) => {
event.preventDefault();
const center = activeCenter();
if (!center) return;
await page.runAction(document.querySelector("#saveScheduleBtn"), "Сохраняю график...", async () => {
const settings = await page.api("/sto/settings/booking", {
method: "POST",
body: JSON.stringify(schedulePayload(event.currentTarget, center.id)),
});
setScheduleForm(settings);
page.toast("График сохранен");
});
});
document.querySelector("#catalogForm").addEventListener("submit", async (event) => {
event.preventDefault();
const center = activeCenter();
if (!center) return;
await page.runAction(document.querySelector("#saveCatalogBtn"), "Добавляю позицию...", async () => {
await page.api("/work-orders/catalog", {
method: "POST",
body: JSON.stringify(catalogPayload(event.currentTarget, center.id)),
});
event.currentTarget.reset();
event.currentTarget.default_quantity.value = 1;
event.currentTarget.default_unit_price.value = 0;
state.catalog = await page.api(`/work-orders/catalog?service_center_id=${center.id}`);
renderCatalog();
page.toast("Позиция добавлена");
});
});
page.boot(async () => {
await loadCenters();
await loadSettings();
});