This commit is contained in:
+402
-32
@@ -451,6 +451,13 @@
|
||||
<i class="fas fa-calendar-alt me-1"></i> События
|
||||
</button>
|
||||
</li>
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="categories-tab" data-bs-toggle="tab" data-bs-target="#categories-tab-pane" type="button" role="tab" aria-controls="categories-tab-pane" aria-selected="false">
|
||||
<i class="fas fa-map-marker-alt me-1"></i> Площадки
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- Содержимое вкладок -->
|
||||
<div class="tab-content" id="mainTabsContent">
|
||||
@@ -573,11 +580,6 @@
|
||||
<button class="btn btn-success" id="publish-all-events-btn">
|
||||
<i class="fas fa-paper-plane me-1"></i> Опубликовать все
|
||||
</button>
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
<button class="btn btn-info ms-2" id="load-categories-btn">
|
||||
<i class="fas fa-sync-alt me-1"></i> Площадки
|
||||
</button>
|
||||
{% endif %}
|
||||
<div id="event-selected-post-info" class="cross-tab-info" style="display: none;">
|
||||
<span>Выбранный пост: </span>
|
||||
<span id="event-selected-post-name"></span>
|
||||
@@ -661,6 +663,83 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
<!-- Вкладка площадок -->
|
||||
<div class="tab-pane fade" id="categories-tab-pane" role="tabpanel" aria-labelledby="categories-tab" tabindex="0">
|
||||
<div class="top-action-buttons d-flex align-items-center">
|
||||
<button class="btn btn-info" id="load-categories-btn">
|
||||
<i class="fas fa-sync-alt me-1"></i> Площадки
|
||||
</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- Левая панель - список площадок -->
|
||||
<div class="col-md-5">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Список площадок</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="input-group me-2" style="width: 200px;">
|
||||
<input type="text" class="form-control form-control-sm" id="categories-search" placeholder="Поиск...">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" id="categories-search-prev">
|
||||
<i class="fas fa-chevron-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" id="categories-search-next">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary" id="refresh-categories-btn">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<!-- Контейнер для фиксированного заголовка -->
|
||||
<div class="table-header-container">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="sortable-header">ID</th>
|
||||
<th class="sortable-header">Название</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Контейнер для скроллируемого тела таблицы -->
|
||||
<div class="scrollable-table-body">
|
||||
<table class="table table-hover mb-0">
|
||||
<tbody id="categories-table">
|
||||
<tr>
|
||||
<td colspan="2" class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
<p class="mt-2">Загрузка данных...</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Правая панель - детали площадки -->
|
||||
<div class="col-md-7">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0" id="category-details-header">Детали площадки</h5>
|
||||
<div id="category-actions"></div>
|
||||
</div>
|
||||
<div class="card-body scrollable-details" id="category-details">
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="fas fa-info-circle fa-3x mb-3"></i>
|
||||
<p>Выберите площадку для просмотра деталей</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Модальное окно подтверждения удаления поста -->
|
||||
@@ -866,6 +945,21 @@
|
||||
syncColumnWidths('events');
|
||||
}, 50);
|
||||
});
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
const categoriesTab = document.getElementById('categories-tab');
|
||||
if (categoriesTab) {
|
||||
categoriesTab.addEventListener('click', () => {
|
||||
if (document.getElementById('categories-table').children.length === 1) {
|
||||
loadCategoriesList();
|
||||
}
|
||||
// Принудительно обновляем размеры панелей и синхронизируем колонки
|
||||
setTimeout(() => {
|
||||
adjustPanelWidthsOnResize();
|
||||
syncColumnWidths('categories');
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
{% endif %}
|
||||
// Новые обработчики для кнопок событий
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
const volkRescanBtn = document.getElementById('volk-rescan-btn');
|
||||
@@ -885,6 +979,10 @@
|
||||
if (loadCategoriesBtn) {
|
||||
loadCategoriesBtn.addEventListener('click', loadCategories);
|
||||
}
|
||||
const refreshCategoriesBtn = document.getElementById('refresh-categories-btn');
|
||||
if (refreshCategoriesBtn) {
|
||||
refreshCategoriesBtn.addEventListener('click', loadCategoriesList);
|
||||
}
|
||||
{% endif %}
|
||||
// Обработчики для кнопок привязки
|
||||
document.getElementById('post-link-event-btn').addEventListener('click', togglePostEventLink);
|
||||
@@ -1034,28 +1132,76 @@
|
||||
}
|
||||
// Функция для загрузки категорий
|
||||
function loadCategories() {
|
||||
showProcessingMessage("Загрузка категорий...");
|
||||
fetch(`${PREFIX}/api/load_categories`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideProcessingMessage();
|
||||
if (data.success) {
|
||||
alert('Категории загружены успешно!');
|
||||
} else {
|
||||
alert('Ошибка загрузки категорий: ' + (data.error || 'неизвестная ошибка'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideProcessingMessage();
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
alert('Произошла ошибка при загрузке категорий');
|
||||
});
|
||||
// Сначала проверяем наличие событий
|
||||
fetch(`${PREFIX}/api/events/count`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const eventCount = data.count || 0;
|
||||
if (eventCount > 0) {
|
||||
// Если есть события, спрашиваем подтверждение
|
||||
if (!confirm(`В таблице событий есть ${eventCount} записей. Обновление данных площадок может повлиять на существующие события. Вы уверены, что хотите продолжить?`)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Продолжаем загрузку категорий
|
||||
showProcessingMessage("Загрузка категорий...");
|
||||
fetch(`${PREFIX}/api/load_categories`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideProcessingMessage();
|
||||
if (data.success) {
|
||||
alert('Категории загружены успешно!');
|
||||
// Обновляем список категорий, если мы на вкладке площадок
|
||||
const categoriesTab = document.getElementById('categories-tab');
|
||||
if (categoriesTab && categoriesTab.classList.contains('active')) {
|
||||
loadCategoriesList();
|
||||
}
|
||||
} else {
|
||||
alert('Ошибка загрузки категорий: ' + (data.error || 'неизвестная ошибка'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideProcessingMessage();
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
alert('Произошла ошибка при загрузке категорий');
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка проверки количества событий:', error);
|
||||
// В случае ошибки все равно продолжаем загрузку
|
||||
showProcessingMessage("Загрузка категорий...");
|
||||
fetch(`${PREFIX}/api/load_categories`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideProcessingMessage();
|
||||
if (data.success) {
|
||||
alert('Категории загружены успешно!');
|
||||
const categoriesTab = document.getElementById('categories-tab');
|
||||
if (categoriesTab && categoriesTab.classList.contains('active')) {
|
||||
loadCategoriesList();
|
||||
}
|
||||
} else {
|
||||
alert('Ошибка загрузки категорий: ' + (data.error || 'неизвестная ошибка'));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
hideProcessingMessage();
|
||||
console.error('Ошибка загрузки категорий:', err);
|
||||
alert('Произошла ошибка при загрузке категорий');
|
||||
});
|
||||
});
|
||||
}
|
||||
// Функция для публикации конкретного события
|
||||
function publishCurrentEvent(eventId) {
|
||||
@@ -1680,6 +1826,142 @@
|
||||
console.error('Ошибка загрузки деталей события:', error);
|
||||
}
|
||||
}
|
||||
// Функции для работы с категориями (площадками)
|
||||
let selectedCategoryId = null;
|
||||
async function loadCategoriesList() {
|
||||
const tableBody = document.getElementById('categories-table');
|
||||
if (!tableBody) return;
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="2" class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
Загрузка площадок...
|
||||
</td>
|
||||
</tr>`;
|
||||
try {
|
||||
const response = await fetch(`${PREFIX}/api/categories`);
|
||||
const categories = await response.json();
|
||||
if (categories.length === 0) {
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="2" class="text-center py-5 text-muted">
|
||||
<i class="fas fa-inbox fa-3x mb-3"></i>
|
||||
<p>Нет доступных площадок</p>
|
||||
</td>
|
||||
</tr>`;
|
||||
return;
|
||||
}
|
||||
tableBody.innerHTML = '';
|
||||
categories.forEach(category => {
|
||||
const row = `
|
||||
<tr class="category-row" data-id="${category.id}">
|
||||
<td>${category.id || '-'}</td>
|
||||
<td class="category-name-preview" title="${category.title || ''}">${category.title || 'Без названия'}</td>
|
||||
</tr>`;
|
||||
tableBody.innerHTML += row;
|
||||
});
|
||||
// Клик по строке категории
|
||||
document.querySelectorAll('.category-row').forEach(row => {
|
||||
row.addEventListener('click', (e) => {
|
||||
const categoryId = row.dataset.id;
|
||||
showCategoryDetails(categoryId);
|
||||
highlightSelectedCategory(categoryId);
|
||||
});
|
||||
});
|
||||
// Переинициализируем разделители колонок для категорий
|
||||
setTimeout(() => {
|
||||
syncColumnWidths('categories');
|
||||
reinitializeColumnResizers('categories');
|
||||
}, 0);
|
||||
} catch (error) {
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="2" class="text-center py-5 text-danger">
|
||||
<i class="fas fa-exclamation-triangle fa-2x mb-3"></i>
|
||||
<p>Ошибка загрузки данных</p>
|
||||
<button class="btn btn-sm btn-outline-primary mt-2" onclick="loadCategoriesList()">
|
||||
Повторить попытку
|
||||
</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
// Выделение выбранной категории
|
||||
function highlightSelectedCategory(categoryId) {
|
||||
// Снимаем выделение со всех категорий
|
||||
document.querySelectorAll('.category-row').forEach(row => {
|
||||
row.classList.remove('active');
|
||||
});
|
||||
// Находим нужную категорию
|
||||
const selectedRow = document.querySelector(`.category-row[data-id="${categoryId}"]`);
|
||||
if (selectedRow) {
|
||||
// Выделяем категорию
|
||||
selectedRow.classList.add('active');
|
||||
selectedCategoryId = categoryId;
|
||||
// Прокручиваем к выбранной категории
|
||||
selectedRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
// Показать детали категории
|
||||
async function showCategoryDetails(categoryId) {
|
||||
const detailsPanel = document.getElementById('category-details');
|
||||
if (!detailsPanel) return;
|
||||
detailsPanel.innerHTML = `
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
<p class="mt-2">Загрузка данных площадки...</p>
|
||||
</div>`;
|
||||
try {
|
||||
const response = await fetch(`${PREFIX}/api/categories/${encodeURIComponent(categoryId)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Площадка не найдена');
|
||||
}
|
||||
const category = await response.json();
|
||||
// Создаем HTML для деталей
|
||||
let detailsHtml = `
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<strong>ID:</strong> ${category.id || 'Не указано'}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<strong>Название:</strong> ${category.title || 'Не указано'}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<strong>Количество событий:</strong> ${category.event_count || 0}
|
||||
</div>
|
||||
</div>`;
|
||||
if (category.description) {
|
||||
detailsHtml += `
|
||||
<div class="mb-4 mt-3">
|
||||
<strong>Описание:</strong>
|
||||
<div class="border p-3 bg-light rounded mt-2">${category.description}</div>
|
||||
</div>`;
|
||||
}
|
||||
detailsHtml += `</div>`;
|
||||
detailsPanel.innerHTML = detailsHtml;
|
||||
// Обновляем заголовок панели
|
||||
const header = document.getElementById('category-details-header');
|
||||
if (header) {
|
||||
header.textContent = `Площадка: ${category.title || category.id || 'Без названия'}`;
|
||||
}
|
||||
} catch (error) {
|
||||
detailsPanel.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
Ошибка загрузки данных площадки: ${error.message}
|
||||
<button class="btn btn-sm btn-outline-primary mt-2" onclick="showCategoryDetails('${categoryId}')">
|
||||
Повторить попытку
|
||||
</button>
|
||||
</div>`;
|
||||
console.error('Ошибка загрузки деталей категории:', error);
|
||||
}
|
||||
}
|
||||
// Функция загрузки постов
|
||||
async function loadPosts() {
|
||||
const tableBody = document.getElementById('posts-table');
|
||||
@@ -2550,11 +2832,17 @@ function setupColumnResizer(resizer, header, tableType = 'posts') {
|
||||
}
|
||||
|
||||
// Применяем ширину к соответствующим ячейкам в теле таблицы
|
||||
const bodyTable = document.querySelector(
|
||||
tableType === 'posts'
|
||||
? '#posts-tab-pane .scrollable-table-body table'
|
||||
: '#events-tab-pane .scrollable-table-body table'
|
||||
);
|
||||
let bodyTableSelector;
|
||||
if (tableType === 'posts') {
|
||||
bodyTableSelector = '#posts-tab-pane .scrollable-table-body table';
|
||||
} else if (tableType === 'events') {
|
||||
bodyTableSelector = '#events-tab-pane .scrollable-table-body table';
|
||||
} else if (tableType === 'categories') {
|
||||
bodyTableSelector = '#categories-tab-pane .scrollable-table-body table';
|
||||
} else {
|
||||
bodyTableSelector = '#posts-tab-pane .scrollable-table-body table';
|
||||
}
|
||||
const bodyTable = document.querySelector(bodyTableSelector);
|
||||
|
||||
if (bodyTable) {
|
||||
const bodyRows = bodyTable.querySelectorAll('tbody tr');
|
||||
@@ -2595,6 +2883,9 @@ function syncColumnWidths(tableType) {
|
||||
} else if (tableType === 'events') {
|
||||
headerContainerSelector = '#events-tab-pane .table-header-container table';
|
||||
bodyContainerSelector = '#events-tab-pane .scrollable-table-body table';
|
||||
} else if (tableType === 'categories') {
|
||||
headerContainerSelector = '#categories-tab-pane .table-header-container table';
|
||||
bodyContainerSelector = '#categories-tab-pane .scrollable-table-body table';
|
||||
} else {
|
||||
console.warn('Неизвестный тип таблицы для синхронизации:', tableType);
|
||||
return;
|
||||
@@ -3088,6 +3379,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
syncColumnWidths('posts');
|
||||
} else if (tabId === 'events-tab-pane') {
|
||||
syncColumnWidths('events');
|
||||
} else if (tabId === 'categories-tab-pane') {
|
||||
syncColumnWidths('categories');
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
@@ -3541,6 +3834,56 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
// Простой поиск по событиям (без тэгов)
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
// Функция поиска по категориям
|
||||
function searchCategories(query) {
|
||||
const tableBody = document.getElementById('categories-table');
|
||||
if (!tableBody) return;
|
||||
const rows = tableBody.querySelectorAll('.category-row');
|
||||
const searchTerm = query.toLowerCase().trim();
|
||||
let foundCount = 0;
|
||||
let firstMatch = null;
|
||||
rows.forEach((row, index) => {
|
||||
const id = row.querySelector('td:first-child').textContent.toLowerCase();
|
||||
const name = row.querySelector('.category-name-preview').textContent.toLowerCase();
|
||||
const matches = id.includes(searchTerm) || name.includes(searchTerm);
|
||||
if (matches) {
|
||||
row.style.display = '';
|
||||
if (foundCount === 0) {
|
||||
firstMatch = row;
|
||||
}
|
||||
foundCount++;
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
// Выделяем первый найденный результат
|
||||
if (firstMatch && searchTerm) {
|
||||
const categoryId = firstMatch.dataset.id;
|
||||
highlightSelectedCategory(categoryId);
|
||||
showCategoryDetails(categoryId);
|
||||
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
}
|
||||
// Навигация по результатам поиска категорий
|
||||
let currentCategorySearchIndex = -1;
|
||||
function navigateCategoriesSearch(direction) {
|
||||
const tableBody = document.getElementById('categories-table');
|
||||
if (!tableBody) return;
|
||||
const visibleRows = Array.from(tableBody.querySelectorAll('.category-row')).filter(row => row.style.display !== 'none');
|
||||
if (visibleRows.length === 0) return;
|
||||
if (direction === 'next') {
|
||||
currentCategorySearchIndex = (currentCategorySearchIndex + 1) % visibleRows.length;
|
||||
} else {
|
||||
currentCategorySearchIndex = (currentCategorySearchIndex - 1 + visibleRows.length) % visibleRows.length;
|
||||
}
|
||||
const selectedRow = visibleRows[currentCategorySearchIndex];
|
||||
const categoryId = selectedRow.dataset.id;
|
||||
highlightSelectedCategory(categoryId);
|
||||
showCategoryDetails(categoryId);
|
||||
selectedRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
{% endif %}
|
||||
function searchEventsSimple(query) {
|
||||
const events = document.querySelectorAll('.event-row');
|
||||
eventsSearchResults = [];
|
||||
@@ -3673,6 +4016,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
{% if g.workmode == 'VOLK' %}
|
||||
const categoriesSearchInput = document.getElementById('categories-search');
|
||||
if (categoriesSearchInput) {
|
||||
categoriesSearchInput.addEventListener('input', (e) => {
|
||||
searchCategories(e.target.value);
|
||||
currentCategorySearchIndex = -1;
|
||||
});
|
||||
document.getElementById('categories-search-prev').addEventListener('click', () => {
|
||||
navigateCategoriesSearch('prev');
|
||||
});
|
||||
document.getElementById('categories-search-next').addEventListener('click', () => {
|
||||
navigateCategoriesSearch('next');
|
||||
});
|
||||
categoriesSearchInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
navigateCategoriesSearch('next');
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
navigateCategoriesSearch('prev');
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
navigateCategoriesSearch('next');
|
||||
}
|
||||
});
|
||||
}
|
||||
{% endif %}
|
||||
document.getElementById('events-search').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user