diff --git a/miniapp/assets/assembly.js b/miniapp/assets/assembly.js
index 90ad221..94c2e00 100644
--- a/miniapp/assets/assembly.js
+++ b/miniapp/assets/assembly.js
@@ -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 = `
Ошибка: ${escHtml(data.error)}
`;
btn.disabled = false;
@@ -247,14 +257,10 @@ const Assembly = (function () {
const loading = el(``);
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(`${escHtml(data.error)}
`));
@@ -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(`Сеть: ${escHtml(e.message)}
`));
diff --git a/miniapp/assets/measurements.js b/miniapp/assets/measurements.js
index 7b05563..2f99d70 100644
--- a/miniapp/assets/measurements.js
+++ b/miniapp/assets/measurements.js
@@ -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(``));
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 = `Ошибка: ${data.error}
`;
btn.disabled = false; btn.textContent = isUpdate ? "Закрыть заявку" : "Сохранить замер";
diff --git a/miniapp/assets/proposals.js b/miniapp/assets/proposals.js
index f2b3709..b9efa33 100644
--- a/miniapp/assets/proposals.js
+++ b/miniapp/assets/proposals.js
@@ -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 ─────────────────────────────────────────────
diff --git a/miniapp/assets/request.js b/miniapp/assets/request.js
index a24882e..046d76c 100644
--- a/miniapp/assets/request.js
+++ b/miniapp/assets/request.js
@@ -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 = `Ошибка: ${data.error}
`;
btn.disabled = false;
diff --git a/miniapp/index.html b/miniapp/index.html
index 8bfbc73..4573615 100644
--- a/miniapp/index.html
+++ b/miniapp/index.html
@@ -41,10 +41,10 @@
-
-
-
-
+
+
+
+