180 lines
7.7 KiB
JavaScript
180 lines
7.7 KiB
JavaScript
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();
|
||
});
|