Add service platform foundation

This commit is contained in:
VPN SaaS Dev
2026-05-12 19:45:08 +09:00
parent 2ba2e88432
commit 34035a27cb
23 changed files with 2199 additions and 18 deletions

View File

@@ -220,6 +220,9 @@
<button class="menu-row" id="openCarProfileBtn">Параметры автомобиля</button>
<button class="menu-row" id="openSettingsBtn">Локаль и валюта</button>
<button class="menu-row" id="openNotificationsBtn">Уведомления</button>
<button class="menu-row" id="openConfirmationsBtn">Запросы на подтверждение</button>
<button class="menu-row" id="openConnectedServicesBtn">Подключенные автосервисы</button>
<button class="menu-row" id="openServicePanelBtn">Панель СТО</button>
<button class="menu-row" id="openScanBtn">Разобрать чек</button>
<section class="drawer-section hidden" id="settingsSection">
@@ -253,6 +256,54 @@
<button type="button" class="wide-btn" id="enableNotificationsBtn">Включить уведомления</button>
</section>
<section class="drawer-section hidden" id="confirmationsSection">
<h2>Запросы на подтверждение</h2>
<div class="tip-card">Здесь будут визиты СТО и изменения данных авто, которые ждут твоего решения.</div>
<div id="confirmationRequests" class="stack-list"></div>
</section>
<section class="drawer-section hidden" id="connectedServicesSection">
<h2>Подключенные автосервисы</h2>
<div class="tip-card">Автосервис видит машину только после твоего разрешения или в рамках визита.</div>
<div id="connectedServices" class="stack-list"></div>
</section>
<section class="drawer-section hidden" id="servicePanelSection">
<h2>Панель СТО</h2>
<form id="serviceCenterForm" class="grid-form drawer-form">
<label>
Название СТО
<input name="display_name" placeholder="Smart Service" required />
</label>
<label>
Юридическое название
<input name="legal_name" placeholder="ООО Smart Service" />
</label>
<label>
Страна
<input name="country" maxlength="2" placeholder="KR" />
</label>
<label>
Город
<input name="city" placeholder="Seoul" />
</label>
<label>
Адрес
<input name="address" />
</label>
<label>
Телефон
<input name="phone" />
</label>
<label>
Регистрационный номер
<input name="business_registration_number" />
</label>
<button type="submit">Создать СТО</button>
</form>
<div id="serviceCentersList" class="stack-list"></div>
</section>
<section class="drawer-section" id="carFormSection">
<h2>Новое авто</h2>
<form id="carForm" class="grid-form drawer-form">

View File

@@ -316,6 +316,7 @@ const state = {
latestStats: null,
allStats: null,
analytics: null,
serviceCenters: [],
receiptFile: null,
serviceWorkerRegistration: null,
period: {
@@ -798,6 +799,36 @@ function openCarProfile() {
document.querySelector("#carProfileSection").scrollIntoView({ behavior: "smooth", block: "start" });
}
async function loadServiceCenters() {
state.serviceCenters = await api("/service-centers/my");
renderServiceCenters();
}
function renderServiceCenters() {
const root = document.querySelector("#serviceCentersList");
if (!root) return;
if (!state.serviceCenters.length) {
root.innerHTML = `<div class="empty">СТО пока не создано</div>`;
return;
}
root.innerHTML = state.serviceCenters
.map(
(center) => `
<div class="stack-item">
<strong>${center.display_name || center.name}</strong>
<small>${[center.city, center.address].filter(Boolean).join(", ") || "Адрес не указан"}</small>
<small>Статус: ${center.verification_status}</small>
</div>
`,
)
.join("");
}
function renderPlaceholderList(selector, message) {
const root = document.querySelector(selector);
if (root) root.innerHTML = `<div class="empty">${message}</div>`;
}
function renderStats(stats) {
const root = document.querySelector("#stats");
if (!stats) {
@@ -1398,6 +1429,49 @@ document.querySelector("#openNotificationsBtn").addEventListener("click", () =>
document.querySelector("#notificationsSection").scrollIntoView({ behavior: "smooth", block: "start" });
});
document.querySelector("#openConfirmationsBtn").addEventListener("click", () => {
document.querySelector("#confirmationsSection").classList.remove("hidden");
renderPlaceholderList("#confirmationRequests", "Новых запросов нет");
document.querySelector("#confirmationsSection").scrollIntoView({ behavior: "smooth", block: "start" });
});
document.querySelector("#openConnectedServicesBtn").addEventListener("click", () => {
document.querySelector("#connectedServicesSection").classList.remove("hidden");
renderPlaceholderList("#connectedServices", "Подключенных автосервисов пока нет");
document.querySelector("#connectedServicesSection").scrollIntoView({ behavior: "smooth", block: "start" });
});
document.querySelector("#openServicePanelBtn").addEventListener("click", async (event) => {
await runAction(event.currentTarget, "Загружаю СТО...", async () => {
document.querySelector("#servicePanelSection").classList.remove("hidden");
await loadServiceCenters();
document.querySelector("#servicePanelSection").scrollIntoView({ behavior: "smooth", block: "start" });
});
});
document.querySelector("#serviceCenterForm").addEventListener("submit", async (event) => {
event.preventDefault();
const form = event.currentTarget;
await runAction(form.querySelector('button[type="submit"]'), "Создаю СТО...", async () => {
const data = formData(form);
await api("/service-centers", {
method: "POST",
body: JSON.stringify({
display_name: data.display_name,
legal_name: data.legal_name || null,
country: data.country || null,
city: data.city || null,
address: data.address || null,
phone: data.phone || null,
business_registration_number: data.business_registration_number || null,
}),
});
form.reset();
await loadServiceCenters();
toast("СТО создано");
});
});
document.querySelector("#enableNotificationsBtn").addEventListener("click", enableNotifications);
document.querySelector("#openScanBtn").addEventListener("click", () => {

View File

@@ -1243,6 +1243,25 @@ select {
font-size: 12px;
}
.stack-list {
display: grid;
gap: 8px;
margin-top: 10px;
}
.stack-item {
display: grid;
gap: 4px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--soft);
}
.stack-item small {
color: var(--muted);
}
@keyframes toastIn {
from {
opacity: 0;