Files
turborfq/public/js/core.js
2026-02-27 16:49:44 +03:00

156 lines
4.3 KiB
JavaScript
Raw 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.
// ===== CORE.JS - API и утилиты =====
/**
* API wrapper для запросов к серверу
*/
async function api(url, method = 'GET', body) {
const opts = { method, headers: { 'Content-Type': 'application/json' } };
if (body) opts.body = JSON.stringify(body);
const res = await fetch(url, opts);
// Пытаемся распарсить как JSON
const contentType = res.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await res.json();
} else {
const txt = await res.text();
// Пытаемся извлечь сообщение из HTML ошибки Slim
const msgMatch = txt.match(/<strong>Message:<\/strong>\s*([^<]+)/);
if (msgMatch) {
throw new Error(msgMatch[1].trim());
}
throw new Error(`Ошибка сервера: ${res.status}`);
}
if (!res.ok) {
// Сервер вернул JSON с ошибкой
const rawMessage = data.message || data.error || 'Неизвестная ошибка';
const cleanMessage = String(rawMessage).replace(/^CONFLICT:\s*/i, '');
throw new Error(cleanMessage);
}
// Проверяем на ошибку в успешном ответе
if (data.error) {
throw new Error(data.message || 'Ошибка');
}
return data;
}
/**
* Обновить счётчик выделенных строк
*/
function updateSelectionCounter() {
const counter = document.getElementById('selectionCounter');
let count = 0;
if (table) {
const allSelectedData = new Map();
table.getSelectedData().forEach(rowData => {
const key = getRowKey(rowData);
allSelectedData.set(key, rowData);
});
selectedRowsDataGlobal.forEach((rowData, key) => {
allSelectedData.set(key, rowData);
});
count = allSelectedData.size;
} else {
count = selectedRowsDataGlobal.size;
}
if (count > 0) {
counter.textContent = `Выбрано: ${count}`;
counter.style.display = 'block';
} else {
counter.style.display = 'none';
}
}
/**
* Создать модальное окно с прогрессом
*/
function createProgressModal(message) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white;
padding: 30px;
border-radius: 8px;
min-width: 300px;
text-align: center;
`;
dialog.innerHTML = `
<div style="font-size: 16px; margin-bottom: 20px;">${message}</div>
<div style="width: 100%; height: 30px; background: #e0e0e0; border-radius: 15px; overflow: hidden;">
<div class="progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, #4CAF50, #45a049); transition: width 0.3s;"></div>
</div>
<div style="margin-top: 10px; font-size: 14px; color: #666;">
<span class="spinner" style="display: inline-block; animation: spin 1s linear infinite;">⏳</span>
Пожалуйста, подождите...
</div>
<style>
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
`;
modal.appendChild(dialog);
const progressBar = dialog.querySelector('.progress-bar');
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress > 90) progress = 90;
progressBar.style.width = progress + '%';
}, 200);
modal.stopProgress = () => {
clearInterval(interval);
progressBar.style.width = '100%';
};
return modal;
}
/**
* Экранировать HTML
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Получить уникальный ключ строки по первичному ключу
*/
function getRowKey(rowData) {
if (!currentMeta || !currentMeta.primaryKey || currentMeta.primaryKey.length === 0) {
return JSON.stringify(rowData);
}
const pkValues = currentMeta.primaryKey.map(pk => rowData[pk]).join('|');
return pkValues;
}
console.log('✅ core.js загружен');