/
delphin
/
CatRW
Обзор
Документация
Войти
/
delphin
/
CatRW
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/models.js
974 строки
41 KB
delphin
перед глобальным изменением
04 мар 2026, 09:56
Верифицирован
04 мар 2026, 09:56
c6cfdf3
Код
Авторство
О чём код?
// /js/models.js - УПРАВЛЕНИЕ МОДЕЛЯМИ (с railway_company как ID) try { // ===== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ===== let allModels = []; let allTypes = []; let allSubtypes = []; let currentlyEditingId = null; let isEditMode = false; const API_BASE = '/api/models'; const API_MODEL_TYPES = '/api/model_types'; const API_MODEL_SUBTYPES = '/api/model_subtypes'; console.log('✅ models.js загружен'); // ===== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===== // 0. Прочитать куку function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) { const cookieValue = parts.pop().split(';').shift(); return cookieValue; } return null; } // 1. Загрузить типы моделей async function loadTypes() { try { const response = await fetch(API_MODEL_TYPES); if (response.ok) { const data = await response.json(); if (data.success) { allTypes = data.data || []; console.log(`📊 Загружено ${allTypes.length} типов моделей`); updateTypeSelects(); } } } catch (error) { console.error('❌ Ошибка загрузки типов:', error); } } // 2. Обновить селекты типов function updateTypeSelects() { const typeSelect = document.getElementById('modelType'); const filterType = document.getElementById('filterType'); if (typeSelect && allTypes.length > 0) { typeSelect.innerHTML = '<option value="">Выберите тип</option>' + allTypes.map(t => `<option value="${t.code}">${escapeHtml(t.code)}</option>` ).join(''); typeSelect.addEventListener('change', function() { loadSubtypesForType(this.value); }); } if (filterType && allTypes.length > 0) { filterType.innerHTML = '<option value="">Все типы</option>' + allTypes.map(t => `<option value="${t.code}">${escapeHtml(t.code)}</option>` ).join(''); } } // 3. Загручить подтипы для типа async function loadSubtypesForType(typeCode) { try { if (!typeCode) { allSubtypes = []; updateSubtypeSelect(); return; } const response = await fetch(`${API_MODEL_SUBTYPES}?type_code=${encodeURIComponent(typeCode)}`); if (response.ok) { const data = await response.json(); if (data.success) { allSubtypes = data.data || []; console.log(`📊 Загружено ${allSubtypes.length} подтипов для типа "${typeCode}"`); updateSubtypeSelect(); } } } catch (error) { console.error('❌ Ошибка загрузки подтипов:', error); } } // 4. Обновить селект подтипов function updateSubtypeSelect() { const subtypeSelect = document.getElementById('modelSubtype'); if (!subtypeSelect) return; subtypeSelect.innerHTML = '<option value="">Выберите подтип</option>'; allSubtypes.forEach(subtype => { const option = document.createElement('option'); option.value = subtype.code; option.textContent = subtype.code; subtypeSelect.appendChild(option); }); } // 5. Загрузить производителей для фильтра async function loadManufacturersForFilter() { try { const response = await fetch('/api/manufacturers'); if (response.ok) { const data = await response.json(); if (data.success) { const select = document.getElementById('filterManufacturer'); if (select && data.data) { // Проверяем URL параметр ДО заполнения select const urlParams = new URLSearchParams(window.location.search); const urlManufacturerId = urlParams.get('manufacturer_id'); select.innerHTML = '<option value="">Все производители</option>'; data.data.forEach(manufacturer => { const option = document.createElement('option'); option.value = manufacturer.id; option.textContent = manufacturer.name; select.appendChild(option); }); if (urlManufacturerId) { select.value = urlManufacturerId; console.log(`✅ Установлен фильтр производителя: ${urlManufacturerId}`); } } } } } catch (error) { console.error('Ошибка загрузки производителей:', error); } } // 6. Загрузить железные дороги для фильтра async function loadRailwaysForFilter() { try { const response = await fetch('/api/railways'); if (response.ok) { const data = await response.json(); if (data.success) { // Для фильтра const filterSelect = document.getElementById('filterRailwayCompany'); if (filterSelect && data.data) { filterSelect.innerHTML = '<option value="">Все железные дороги</option>'; data.data.forEach(railway => { const option = document.createElement('option'); option.value = railway.id; // <-- ИСПОЛЬЗУЕМ ID option.textContent = railway.name; if (railway.country_code) { option.textContent += ` (${railway.country_code})`; } filterSelect.appendChild(option); }); } // Для формы добавления/редактирования const formSelect = document.getElementById('modelRailwayCompany'); if (formSelect && data.data) { formSelect.innerHTML = '<option value="">Выберите железную дорогу</option>'; data.data.forEach(railway => { const option = document.createElement('option'); option.value = railway.id; // <-- ИСПОЛЬЗУЕМ ID option.textContent = railway.name; if (railway.country_code) { option.textContent += ` (${railway.country_code})`; } formSelect.appendChild(option); }); } } } } catch (error) { console.error('Ошибка загрузки железных дорог:', error); } } // 7. Загрузить модели (основная функция) window.loadModels = async function() { console.log('🔄 Загрузка моделей...'); const tbody = document.getElementById('modelsList'); if (!tbody) return; showLoading(tbody, 'Загрузка моделей...'); try { // Загружаем справочники await Promise.all([ loadManufacturersForFilter(), loadRailwaysForFilter(), loadTypes() ]); // Получаем фильтры из формы const filters = getFilters(); // Проверяем URL на наличие параметров const urlParams = new URLSearchParams(window.location.search); const urlManufacturerId = urlParams.get('manufacturer_id'); const urlRailwayCompanyId = urlParams.get('railway_company'); // <-- ТЕПЕРЬ ОЖИДАЕТСЯ ID if (urlManufacturerId) { console.log(`📌 Фильтр производителя из URL: ${urlManufacturerId}`); filters.manufacturer_id = urlManufacturerId; const manufacturerSelect = document.getElementById('filterManufacturer'); if (manufacturerSelect) manufacturerSelect.value = urlManufacturerId; } if (urlRailwayCompanyId) { console.log(`📌 Фильтр железной дороги из URL (ID): ${urlRailwayCompanyId}`); filters.railway_company = urlRailwayCompanyId; // <-- ТЕПЕРЬ ПЕРЕДАЕМ ID const railwaySelect = document.getElementById('filterRailwayCompany'); if (railwaySelect) railwaySelect.value = urlRailwayCompanyId; } // Строим URL с параметрами const params = new URLSearchParams(); for (const key in filters) { if (filters[key]) { params.append(key, filters[key]); } } const page = document.getElementById('currentPage')?.value || 1; params.append('page', page); params.append('per_page', 20); params.append('action', 'list'); const url = `${API_BASE}?${params.toString()}`; console.log('Запрос моделей:', url); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); console.log('📊 Получены данные моделей:', data); if (data && data.success) { allModels = data.data || []; renderModelsTable(allModels); updateTableInfo({ total: data.total || allModels.length, page: data.page || 1, pages: data.pages || 1 }); Notifications.success(`✅ Загружено: ${allModels.length} моделей`); } else { showError(tbody, data?.error || 'Неверный формат ответа'); } } catch (error) { console.error('❌ Ошибка загрузки:', error); showError(tbody, 'Ошибка: ' + error.message); } } // 8. Получить фильтры из формы function getFilters() { const filters = {}; const getValue = (id) => { const elem = document.getElementById(id); if (!elem) return ''; if (elem.tagName === 'SELECT' && elem.options.length === 0) { console.warn(`Select ${id} еще не заполнен`); return ''; } return elem.value; }; filters.type_code = getValue('filterType'); filters.scale_code = getValue('filterScale'); filters.manufacturer_id = getValue('filterManufacturer'); filters.railway_company = getValue('filterRailwayCompany'); // <-- ТЕПЕРЬ ID filters.era_code = getValue('filterEra'); filters.status = getValue('filterStatus'); console.log('🔍 Фильтры:', filters); // Очищаем пустые значения Object.keys(filters).forEach(key => { if (!filters[key]) delete filters[key]; }); return filters; } // 9. Отрендерить таблицу моделей function renderModelsTable(models) { const tbody = document.getElementById('modelsList'); if (!tbody) return; if (!models || !Array.isArray(models) || models.length === 0) { tbody.innerHTML = ` <tr><td colspan="9" style="text-align: center; padding: 40px; color: #7f8c8d;"> Нет моделей </td></tr> `; return; } let html = ''; models.forEach(item => { if (!item) return; // Статус модели const statusBadge = getStatusBadge(item.status || 'draft'); // Производитель const manufacturer = item.manufacturer_name || '—'; const manufacturerCountry = item.manufacturer_country ? `${getCountryFlag(item.manufacturer_country)} ${item.manufacturer_country}` : ''; // Железная дорога (теперь это ID в базе, но получаем название из join) const railwayCompany = item.railway_company_name || '—'; const railwayCompanyCode = item.railway_company_code ? `<small style="color: #7f8c8d;">(${escapeHtml(item.railway_company_code)})</small>` : ''; // Масштаб const scaleBadge = item.scale_code ? `<span class="badge badge-primary">${escapeHtml(item.scale_code)}</span>` : '—'; // Цена const priceHtml = item.rec_price ? `<strong>${item.rec_price} ${item.currency_code || 'EUR'}</strong>` : '—'; // Особенности const features = []; if (item.has_dcc_decoder) features.push('DCC'); if (item.has_sound) features.push('Sound'); if (item.has_lights) features.push('Light'); const featuresHtml = features.length > 0 ? `<div style="font-size: 11px; color: #666;">${features.join(', ')}</div>` : ''; html += ` <tr id="model-row-${item.id}"> <td><strong>#${item.id}</strong></td> <td> <strong>${escapeHtml(item.name)}</strong> ${item.model_number ? `<div style="font-size: 12px; color: #666;">Арт: ${escapeHtml(item.model_number)}</div>` : ''} ${featuresHtml} </td> <td> <div>${escapeHtml(item.type_code || '—')}</div> ${item.subtype_code ? `<div style="font-size: 12px; color: #666;">${escapeHtml(item.subtype_code)}</div>` : ''} </td> <td>${scaleBadge}</td> <td> <div>${escapeHtml(manufacturer)}</div> <div style="font-size: 12px; color: #666;">${manufacturerCountry}</div> </td> <td> <div>${escapeHtml(railwayCompany)} ${railwayCompanyCode}</div> </td> <td>${escapeHtml(item.era_code || '—')}</td> <td>${priceHtml}</td> <td>${statusBadge}</td> <td> <div class="action-buttons"> <button onclick="openEditModelForm(${item.id})" class="btn btn-warning btn-small" title="Редактировать"> ✏️ </button> <button onclick="deleteModel(${item.id})" class="btn btn-danger btn-small" title="Удалить"> 🗑️ </button> </div> </td> </tr> `; }); tbody.innerHTML = html; } // 10. Получить бейдж статуса function getStatusBadge(status) { const badges = { 'published': { text: 'Опубликовано', class: 'badge-success' }, 'draft': { text: 'Черновик', class: 'badge-warning' }, 'archived': { text: 'Архив', class: 'badge-secondary' } }; const badge = badges[status] || { text: status, class: 'badge-primary' }; return `<span class="badge ${badge.class}">${badge.text}</span>`; } // 11. Открыть форму добавления window.openAddModelForm = function() { const form = document.getElementById('addModelFormContainer'); if (!form) return; // 1. Сначала показываем форму form.style.display = 'block'; // 2. Очищаем форму (но нужно убедиться, что clearAddModelForm не трогает modelScale) clearAddModelForm(); // 3. ПОТОМ устанавливаем масштаб из куки const activeScale = getCookie('active_scale'); if (activeScale) { document.getElementById('modelScale').value = activeScale; } const userId = document.getElementById('currentUserId')?.value; if (userId) { document.getElementById('modelCreatedBy').value = userId; } setTimeout(() => { document.getElementById('modelName')?.focus(); }, 100); } // 12. Закрыть форму window.closeAddModelForm = function() { const form = document.getElementById('addModelFormContainer'); if (form) form.style.display = 'none'; cancelEditModelMode(); } // 13. Очистить форму function clearAddModelForm() { if (isEditMode) { console.log('⚠️ Пропускаем очистку: режим редактирования активен'); return; } const clearField = (id) => { const elem = document.getElementById(id); if (elem) elem.value = ''; }; [ 'modelName', 'modelNumber', 'modelType', 'modelSubtype', 'modelScale', 'modelEra', 'modelManufacturer', 'modelRoadNumber', 'modelRailwayCompany', 'modelCountry', 'modelLength', 'modelWeight', 'modelCoupling', 'modelPrice', 'modelCurrency', 'modelDescription', 'modelPrototypeInfo' ].forEach(clearField); ['modelDCC', 'modelSound', 'modelLights'].forEach(id => { const elem = document.getElementById(id); if (elem) elem.checked = false; }); const statusSelect = document.getElementById('modelStatus'); if (statusSelect) statusSelect.value = 'draft'; isEditMode = false; currentlyEditingId = null; const formTitle = document.getElementById('modelFormTitle'); const saveButton = document.getElementById('saveModelButton'); if (formTitle) formTitle.textContent = '📝 Новая модель'; if (saveButton) { saveButton.textContent = '➕ Добавить'; saveButton.onclick = saveModel; } const cancelBtn = document.getElementById('cancelModelEditBtn'); if (cancelBtn) cancelBtn.style.display = 'none'; } // 14. Сохранить модель (универсальная функция) window.saveModel = function() { if (isEditMode) { return saveEditModel(); } else { return saveNewModel(); } } // 15. Сохранить новую модель async function saveNewModel() { const nameInput = document.getElementById('modelName'); if (!nameInput) return; const name = nameInput.value.trim(); if (!name) { Notifications.error('❌ Введите название модели'); return; } const manufacturerId = document.getElementById('modelManufacturer')?.value; if (!manufacturerId) { Notifications.error('❌ Выберите производителя'); return; } const typeCode = document.getElementById('modelType')?.value; if (!typeCode) { Notifications.error('❌ Выберите тип модели'); return; } // Собираем данные (railway_company теперь ID) const data = { name: name, manufacturer_id: parseInt(manufacturerId), type_code: typeCode, subtype_code: document.getElementById('modelSubtype')?.value || '', //scale_code: document.getElementById('modelScale')?.value || '', era_code: document.getElementById('modelEra')?.value || '', model_number: document.getElementById('modelNumber')?.value.trim() || '', road_number: document.getElementById('modelRoadNumber')?.value.trim() || '', railway_company: document.getElementById('modelRailwayCompany')?.value || null, // <-- ТЕПЕРЬ ID country_code: document.getElementById('modelCountry')?.value.trim() || '', length_mm: document.getElementById('modelLength')?.value || null, weight_gr: document.getElementById('modelWeight')?.value || null, coupling_code: document.getElementById('modelCoupling')?.value || '', has_dcc_decoder: document.getElementById('modelDCC')?.checked ? 1 : 0, has_sound: document.getElementById('modelSound')?.checked ? 1 : 0, has_lights: document.getElementById('modelLights')?.checked ? 1 : 0, rec_price: document.getElementById('modelPrice')?.value || null, currency_code: document.getElementById('modelCurrency')?.value || 'EUR', description: document.getElementById('modelDescription')?.value.trim() || '', prototype_info: document.getElementById('modelPrototypeInfo')?.value.trim() || '', status: document.getElementById('modelStatus')?.value || 'draft', created_by: document.getElementById('modelCreatedBy')?.value || null }; console.log('📤 Добавление модели:', data); Notifications.info('📤 Отправка данных...'); try { const response = await fetch(API_BASE, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); console.log('📊 Результат:', result); if (result.success) { Notifications.success(`✅ Модель добавлена! ID: ${result.id}`); setTimeout(() => { closeAddModelForm(); loadModels(); }, 1500); } else { Notifications.error(`❌ ${result.error || 'Ошибка'}`); } } catch (error) { console.error('❌ Ошибка:', error); Notifications.error(`❌ ${error.message}`); } } // 16. Открыть форму редактирования window.openEditModelForm = async function(id) { console.log('📝 Открытие формы редактирования модели ID:', id); Notifications.info('📥 Загрузка данных модели...'); try { const response = await fetch(`/api/models/${id}`); const result = await response.json(); if (result.success) { const model = result.data; console.log('Модель загружена из API:', model); isEditMode = true; currentlyEditingId = id; // Заполняем форму document.getElementById('modelName').value = model.name || ''; document.getElementById('modelNumber').value = model.model_number || ''; document.getElementById('modelType').value = model.type_code || ''; document.getElementById('modelSubtype').value = model.subtype_code || ''; document.getElementById('modelScale').value = model.scale_code || ''; document.getElementById('modelEra').value = model.era_code || ''; document.getElementById('modelManufacturer').value = model.manufacturer_id || ''; document.getElementById('modelRoadNumber').value = model.road_number || ''; document.getElementById('modelRailwayCompany').value = model.railway_company || ''; // <-- ТЕПЕРЬ ID document.getElementById('modelCountry').value = model.country_code || ''; document.getElementById('modelLength').value = model.length_mm || ''; document.getElementById('modelWeight').value = model.weight_gr || ''; document.getElementById('modelCoupling').value = model.coupling_code || ''; document.getElementById('modelDCC').checked = model.has_dcc_decoder == 1; document.getElementById('modelSound').checked = model.has_sound == 1; document.getElementById('modelLights').checked = model.has_lights == 1; document.getElementById('modelPrice').value = model.rec_price || ''; document.getElementById('modelCurrency').value = model.currency_code || 'EUR'; document.getElementById('modelDescription').value = model.description || ''; document.getElementById('modelPrototypeInfo').value = model.prototype_info || ''; document.getElementById('modelStatus').value = model.status || 'draft'; document.getElementById('modelCreatedBy').value = model.created_by || ''; // Обновляем заголовок const formTitle = document.getElementById('modelFormTitle'); if (formTitle) { formTitle.textContent = `📝 Редактирование: ${model.name}`; } // Обновляем кнопку const saveButton = document.getElementById('saveModelButton'); if (saveButton) { saveButton.textContent = '💾 Сохранить изменения'; saveButton.onclick = saveModel; } // Показываем кнопку отмены const cancelBtn = document.getElementById('cancelModelEditBtn'); if (cancelBtn) { cancelBtn.style.display = 'inline-block'; } // Открываем форму если закрыта const form = document.getElementById('addModelFormContainer'); if (form && form.style.display !== 'block') { form.style.display = 'block'; } // Загружаем подтипы для выбранного типа const typeCode = model.type_code; if (typeCode) { await loadSubtypesForType(typeCode); setTimeout(() => { const subtypeSelect = document.getElementById('modelSubtype'); if (subtypeSelect && model.subtype_code) { subtypeSelect.value = model.subtype_code; } }, 300); } Notifications.info('📝 Режим редактирования'); } else { Notifications.error(`❌ ${result.error || 'Модель не найдена'}`); } } catch (error) { Notifications.error(`❌ Ошибка загрузки: ${error.message}`); } } // 17. Сохранить изменения модели async function saveEditModel() { if (!isEditMode || !currentlyEditingId) { Notifications.error('❌ Не в режиме редактирования'); return; } const nameInput = document.getElementById('modelName'); if (!nameInput) return; const name = nameInput.value.trim(); if (!name) { Notifications.error('❌ Введите название модели'); return; } const manufacturerId = document.getElementById('modelManufacturer')?.value; if (!manufacturerId) { Notifications.error('❌ Выберите производителя'); return; } const typeCode = document.getElementById('modelType')?.value; if (!typeCode) { Notifications.error('❌ Выберите тип модели'); return; } // Собираем данные (railway_company теперь ID) const data = { name: name, manufacturer_id: parseInt(manufacturerId), type_code: typeCode, subtype_code: document.getElementById('modelSubtype')?.value || '', scale_code: document.getElementById('modelScale')?.value || '', era_code: document.getElementById('modelEra')?.value || '', model_number: document.getElementById('modelNumber')?.value.trim() || '', road_number: document.getElementById('modelRoadNumber')?.value.trim() || '', railway_company: document.getElementById('modelRailwayCompany')?.value || null, // <-- ТЕПЕРЬ ID country_code: document.getElementById('modelCountry')?.value.trim() || '', length_mm: document.getElementById('modelLength')?.value || null, weight_gr: document.getElementById('modelWeight')?.value || null, coupling_code: document.getElementById('modelCoupling')?.value || '', has_dcc_decoder: document.getElementById('modelDCC')?.checked ? 1 : 0, has_sound: document.getElementById('modelSound')?.checked ? 1 : 0, has_lights: document.getElementById('modelLights')?.checked ? 1 : 0, rec_price: document.getElementById('modelPrice')?.value || null, currency_code: document.getElementById('modelCurrency')?.value || 'EUR', description: document.getElementById('modelDescription')?.value.trim() || '', prototype_info: document.getElementById('modelPrototypeInfo')?.value.trim() || '', status: document.getElementById('modelStatus')?.value || 'draft' }; console.log('📤 Обновление модели ID:', currentlyEditingId); console.log('Данные:', data); Notifications.info('📤 Сохранение изменений...'); try { const response = await fetch(`/api/models/${currentlyEditingId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { Notifications.success('✅ Модель обновлена'); setTimeout(() => { closeAddModelForm(); loadModels(); }, 1500); } else { Notifications.error(`❌ ${result.error || 'Ошибка'}`); } } catch (error) { Notifications.error(`❌ ${error.message}`); } } // 18. Отменить редактирование window.cancelEditModelMode = function() { isEditMode = false; currentlyEditingId = null; clearAddModelForm(); } // 19. Удалить модель window.deleteModel = async function(id) { const model = allModels.find(m => m && m.id === id); if (!model) return; if (!confirm(`Удалить модель "${model.name}"?`)) return; try { const response = await fetch(`/api/models/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { Notifications.success(`✅ ${result.message}`); loadModels(); if (currentlyEditingId === id) { cancelEditModelMode(); } } else { Notifications.error(`❌ ${result.error || 'Ошибка'}`); } } catch (error) { Notifications.error(`❌ ${error.message}`); } } // 20. Поиск моделей window.searchModels = function() { const query = document.getElementById('modelsSearch')?.value.trim(); if (query) { console.log('Поиск:', query); } else { loadModels(); } } // 21. Применить фильтры window.applyFilters = function() { loadModels(); } // 22. Сбросить фильтры window.resetFilters = function() { ['filterType', 'filterScale', 'filterManufacturer', 'filterRailwayCompany', 'filterEra', 'filterStatus'].forEach(id => { const elem = document.getElementById(id); if (elem) elem.value = ''; }); loadModels(); } // 23. Перейти на страницу window.goToPage = function(page) { document.getElementById('currentPage').value = page; loadModels(); } // ===== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ===== function showLoading(element, message) { if (!element) return; element.innerHTML = ` <tr><td colspan="9" style="text-align: center; padding: 30px; color: #7f8c8d;"> <div style="display: inline-block; animation: spin 1s linear infinite; margin-right: 10px;">⟳</div> ${escapeHtml(message)} </td></tr> `; } function showError(element, message) { if (!element) return; element.innerHTML = ` <tr><td colspan="9" style="text-align: center; padding: 30px; color: #e74c3c;"> ❌ ${escapeHtml(message)} </td></tr> `; } function updateTableInfo(data) { if (!data) return; const setText = (id, text) => { const elem = document.getElementById(id); if (elem) elem.textContent = text; }; setText('totalModels', data.total || 0); setText('currentPage', data.page || 1); setText('totalPages', data.pages || 1); setText('lastUpdate', new Date().toLocaleTimeString()); updatePagination(data.page || 1, data.pages || 1); } function updatePagination(currentPage, totalPages) { const pagination = document.getElementById('pagination'); const paginationBottom = document.getElementById('paginationBottom'); if (!pagination && !paginationBottom) return; if (totalPages <= 1) { if (pagination) pagination.innerHTML = ''; if (paginationBottom) paginationBottom.innerHTML = ''; return; } let html = ''; const maxVisible = 5; let start = Math.max(1, currentPage - Math.floor(maxVisible / 2)); let end = Math.min(totalPages, start + maxVisible - 1); if (end - start + 1 < maxVisible) { start = Math.max(1, end - maxVisible + 1); } if (currentPage > 1) { html += `<button onclick="goToPage(${currentPage - 1})" class="page-link">←</button>`; } if (start > 1) { html += `<button onclick="goToPage(1)" class="page-link">1</button>`; if (start > 2) html += `<span class="page-dots">...</span>`; } for (let i = start; i <= end; i++) { if (i === currentPage) { html += `<span class="page-link active">${i}</span>`; } else { html += `<button onclick="goToPage(${i})" class="page-link">${i}</button>`; } } if (end < totalPages) { if (end < totalPages - 1) html += `<span class="page-dots">...</span>`; html += `<button onclick="goToPage(${totalPages})" class="page-link">${totalPages}</button>`; } if (currentPage < totalPages) { html += `<button onclick="goToPage(${currentPage + 1})" class="page-link">→</button>`; } if (pagination) pagination.innerHTML = html; if (paginationBottom) paginationBottom.innerHTML = html; } function getCountryFlag(code) { if (!code) return ''; const flags = { 'DE': '🇩🇪', 'AT': '🇦🇹', 'CH': '🇨🇭', 'FR': '🇫🇷', 'IT': '🇮🇹', 'ES': '🇪🇸', 'GB': '🇬🇧', 'US': '🇺🇸', 'JP': '🇯🇵', 'CN': '🇨🇳', 'RU': '🇷🇺', 'PL': '🇵🇱' }; return flags[code.toUpperCase()] || ''; } function escapeHtml(text) { if (!text) return ''; return text.toString() .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // 24. Применить фильтры из URL function applyFiltersFromUrl() { const urlParams = new URLSearchParams(window.location.search); console.log('🔍 Применение фильтров из URL:', Object.fromEntries(urlParams.entries())); const paramToElement = { 'manufacturer_id': 'filterManufacturer', 'railway_company': 'filterRailwayCompany', // <-- ТЕПЕРЬ ID 'type_code': 'filterType', 'scale_code': 'filterScale', 'era_code': 'filterEra', 'status': 'filterStatus', 'q': 'modelsSearch' }; let hasFilters = false; for (const [param, elementId] of Object.entries(paramToElement)) { const value = urlParams.get(param); if (value) { const element = document.getElementById(elementId); if (element) { element.value = value; console.log(`✅ Установлен ${param}=${value} в ${elementId}`); hasFilters = true; } } } return hasFilters; } // ===== ИНИЦИАЛИЗАЦИЯ ===== document.addEventListener('DOMContentLoaded', async function() { console.log('✅ DOM загружен, инициализация models.js'); try { await Promise.all([ loadTypes(), loadManufacturersForFilter() ]); const hasUrlFilters = applyFiltersFromUrl(); if (hasUrlFilters) { setTimeout(() => { console.log('🚀 Автоматическое применение фильтров из URL'); applyFilters(); }, 100); } else { loadModels(); } } catch (error) { console.error('❌ Ошибка инициализации:', error); } }); console.log('✅ models.js готов к работе'); } catch (error) { console.error('❌ Критическая ошибка в models.js:', error); alert('Ошибка загрузки скрипта каталога: ' + error.message); }