mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 16:44:48 +00:00
fix: добавить 15-секундный таймаут fetch во все модули (measurements, assembly, proposals, request)
Кнопки больше не зависают бесконечно при медленном или недоступном бэкенде. AbortController + дружелюбное сообщение «Сервер не отвечает — попробуйте ещё раз». Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
40e2275949
commit
0fb0597b8d
@ -17,6 +17,20 @@ const Assembly = (function () {
|
||||
manager_note: "",
|
||||
};
|
||||
|
||||
async function _fetchWithTimeout(url, body, timeoutMs = 15000) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) });
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз");
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function mount(container) {
|
||||
root = container;
|
||||
document.body.classList.remove("has-bottom-nav");
|
||||
@ -198,11 +212,7 @@ const Assembly = (function () {
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/assembly_create`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_create`, body);
|
||||
if (data.error) {
|
||||
result.innerHTML = `<div class="error">Ошибка: ${escHtml(data.error)}</div>`;
|
||||
btn.disabled = false;
|
||||
@ -247,14 +257,10 @@ const Assembly = (function () {
|
||||
const loading = el(`<div class="loader-inline"><div class="spinner"></div></div>`);
|
||||
root.appendChild(loading);
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/assembly_list`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_list`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
loading.remove();
|
||||
if (data.error) {
|
||||
root.appendChild(el(`<div class="error">${escHtml(data.error)}</div>`));
|
||||
@ -309,15 +315,11 @@ const Assembly = (function () {
|
||||
root.appendChild(loading);
|
||||
let a;
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/assembly_detail`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
a = await _fetchWithTimeout(`${BACKEND_URL}/api/assembly_detail`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
assembly_id: id,
|
||||
}),
|
||||
});
|
||||
a = await res.json();
|
||||
});
|
||||
} catch (e) {
|
||||
loading.remove();
|
||||
root.appendChild(el(`<div class="error">Сеть: ${escHtml(e.message)}</div>`));
|
||||
|
||||
@ -94,20 +94,30 @@ const Measurements = (function () {
|
||||
render();
|
||||
}
|
||||
|
||||
async function _fetchWithTimeout(url, body, timeoutMs = 15000) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) });
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз");
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRequestAndStart() {
|
||||
root.innerHTML = "";
|
||||
root.appendChild(renderHeader("Закрыть заявку"));
|
||||
root.appendChild(el(`<div class="loader-inline"><div class="spinner"></div></div>`));
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/measurement_detail`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_detail`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
measurement_id: measurementId,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
if (data.error) {
|
||||
root.innerHTML = "";
|
||||
root.appendChild(renderHeader("Ошибка"));
|
||||
@ -332,14 +342,10 @@ const Measurements = (function () {
|
||||
|
||||
async function fetchNextZamerNo(node) {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/measurement_next_no`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_next_no`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
const hint = node.querySelector("#zamerNoHint");
|
||||
const input = node.querySelector("#zamerNoInput");
|
||||
if (data.ok && data.next_no && input && !state.zamer_no) {
|
||||
@ -529,14 +535,10 @@ const Measurements = (function () {
|
||||
|
||||
let clients = [];
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/clients`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/clients`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
clients = (data.clients || []).sort((a, b) =>
|
||||
(a.client_name || "").localeCompare(b.client_name || "", "ru")
|
||||
);
|
||||
@ -937,15 +939,11 @@ const Measurements = (function () {
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/measurement`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
measurement,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
if (data.error) {
|
||||
result.innerHTML = `<div class="error">Ошибка: ${data.error}</div>`;
|
||||
btn.disabled = false; btn.textContent = isUpdate ? "Закрыть заявку" : "Сохранить замер";
|
||||
|
||||
@ -19,13 +19,23 @@ const Proposals = (function () {
|
||||
return { initData: tg?.initData || "", initDataUnsafe: tg?.initDataUnsafe || null };
|
||||
}
|
||||
|
||||
async function apiFetch(path, extra = {}) {
|
||||
const res = await fetch(`${BACKEND_URL}/api/${path}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...authBody(), ...extra }),
|
||||
});
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
async function apiFetch(path, extra = {}, timeoutMs = 15000) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/${path}`, {
|
||||
method: "POST",
|
||||
signal: ctrl.signal,
|
||||
body: JSON.stringify({ ...authBody(), ...extra }),
|
||||
});
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
} catch (e) {
|
||||
if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз");
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Constants ─────────────────────────────────────────────
|
||||
|
||||
@ -15,6 +15,20 @@ const MeasurementRequest = (function () {
|
||||
};
|
||||
let measurers = [];
|
||||
|
||||
async function _fetchWithTimeout(url, body, timeoutMs = 15000) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(url, { method: "POST", signal: ctrl.signal, body: JSON.stringify(body) });
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
if (e.name === "AbortError") throw new Error("Сервер не отвечает — попробуйте ещё раз");
|
||||
throw e;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function mount(container) {
|
||||
root = container;
|
||||
document.body.classList.remove("has-bottom-nav");
|
||||
@ -116,11 +130,9 @@ const MeasurementRequest = (function () {
|
||||
|
||||
async function loadMeasurers() {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/staff_list`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ initData: tg?.initData || "", role: "measurer" }),
|
||||
});
|
||||
const data = await res.json();
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/staff_list`, {
|
||||
initData: tg?.initData || "", role: "measurer",
|
||||
});
|
||||
measurers = data.staff || [];
|
||||
const sel = document.getElementById("measurerSelect");
|
||||
const hint = document.getElementById("measurerHint");
|
||||
@ -163,21 +175,16 @@ const MeasurementRequest = (function () {
|
||||
result.innerHTML = "";
|
||||
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/measurement_request`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
const data = await _fetchWithTimeout(`${BACKEND_URL}/api/measurement_request`, {
|
||||
initData: tg?.initData || "",
|
||||
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||
client_name: name,
|
||||
client_phone: phone,
|
||||
address: state.address || "",
|
||||
assigned_to_tg_id: state.assigned_to_tg_id || "",
|
||||
// Примечание (рекомендации по дате + особенности) — единое поле
|
||||
preferred_note: state.preferred_note || "",
|
||||
preferred_type: "tbd",
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
});
|
||||
if (data.error) {
|
||||
result.innerHTML = `<div class="error">Ошибка: ${data.error}</div>`;
|
||||
btn.disabled = false;
|
||||
|
||||
@ -41,10 +41,10 @@
|
||||
<script src="assets/podbor.js?v=20260517d"></script>
|
||||
<script src="assets/clients.js?v=20260518e"></script>
|
||||
<script src="assets/zamer-picts.js?v=20260516h"></script>
|
||||
<script src="assets/measurements.js?v=20260517d"></script>
|
||||
<script src="assets/request.js?v=20260517d"></script>
|
||||
<script src="assets/assembly.js?v=20260517d"></script>
|
||||
<script src="assets/proposals.js?v=20260516h"></script>
|
||||
<script src="assets/measurements.js?v=20260518f"></script>
|
||||
<script src="assets/request.js?v=20260518f"></script>
|
||||
<script src="assets/assembly.js?v=20260518f"></script>
|
||||
<script src="assets/proposals.js?v=20260518f"></script>
|
||||
<script src="assets/app.js?v=20260517d"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user