// ===== 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(/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 = `
${message}
Пожалуйста, подождите...
`; 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 загружен');