diff --git a/db_edit.py b/db_edit.py index aeca913..aa06191 100644 --- a/db_edit.py +++ b/db_edit.py @@ -1215,6 +1215,74 @@ def api_get_all_interlinks(): finally: db.close() +# Новые API endpoints для работы с площадками +@bp.route('/api/venues') +@login_required +def api_venues(): + """Получает список всех уникальных площадок с количеством событий""" + db = get_db() + try: + with db.conn.cursor() as cursor: + cursor.execute(""" + SELECT unit_name as name, COUNT(*) as event_count + FROM events + WHERE unit_name IS NOT NULL AND unit_name != '' + GROUP BY unit_name + ORDER BY unit_name + """) + venues = cursor.fetchall() + + venues_list = [] + for venue in venues: + venues_list.append({ + 'name': venue['name'], + 'event_count': venue['event_count'] + }) + + return jsonify(venues_list) + except Exception as e: + return jsonify({'error': str(e)}), 500 + finally: + db.close() + +@bp.route('/api/venues//events') +@login_required +def api_venue_events(venue_name): + """Получает список событий для указанной площадки с информацией о привязке к посту""" + post_id = request.args.get('post_id') + if not post_id: + return jsonify({'error': 'post_id parameter is required'}), 400 + + db = get_db() + try: + with db.conn.cursor() as cursor: + # Получаем события площадки с информацией о привязке к посту + cursor.execute(""" + SELECT e.id, e.name, e.number, e.unit_name, + CASE WHEN i.post_id IS NOT NULL THEN 1 ELSE 0 END as linked + FROM events e + LEFT JOIN interlinks i ON e.id = i.event_id AND i.post_id = %s + WHERE e.unit_name = %s + ORDER BY e.name + """, (post_id, venue_name)) + events = cursor.fetchall() + + events_list = [] + for event in events: + events_list.append({ + 'id': event['id'], + 'name': event['name'], + 'number': event['number'], + 'unit_name': event['unit_name'], + 'linked': bool(event['linked']) + }) + + return jsonify(events_list) + except Exception as e: + return jsonify({'error': str(e)}), 500 + finally: + db.close() + @bp.route('/image_proxy') @login_required def image_proxy(): diff --git a/templates/index.html b/templates/index.html index c68245c..a6f7db1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -430,6 +430,9 @@ Выбранное событие: +
@@ -624,6 +627,53 @@
+ + + + + + +
@@ -1588,6 +1638,8 @@ } // Обновляем информацию о выбранном посте на вкладке событий updateSelectedPostInfo(); + // Обновляем информацию о выбранном событии на вкладке постов (для показа кнопки "Площадки") + updateSelectedEventInfo(); } // Показать детали поста async function showPostDetails(postId) { @@ -1865,12 +1917,21 @@ function updateSelectedEventInfo() { const eventInfoElement = document.getElementById('post-selected-event-info'); const eventNameElement = document.getElementById('post-selected-event-name'); + const venuesBtn = document.getElementById('post-venues-btn'); + if (selectedEventData) { eventNameElement.textContent = selectedEventData.name || `Событие ${selectedEventData.id}`; eventInfoElement.style.display = 'flex'; + // Показываем кнопку "Площадки" только если выбран пост + if (selectedPostId) { + venuesBtn.style.display = 'inline-block'; + } else { + venuesBtn.style.display = 'none'; + } updateInterlinkInfo(); } else { eventInfoElement.style.display = 'none'; + venuesBtn.style.display = 'none'; } } // Добавляем функции для отвязывания постов от событий и наоборот @@ -2563,6 +2624,193 @@ document.addEventListener('DOMContentLoaded', () => { adjustPanelWidthsOnResize(); }, 100); // Задержка 100ms }); + + // Функции для работы с площадками + let selectedVenueName = ''; + let selectedVenueEvents = []; + let currentPostId = null; + + // Обработчик клика по кнопке "Площадки" + document.getElementById('post-venues-btn').addEventListener('click', async () => { + if (!selectedPostId) { + alert('Сначала выберите пост'); + return; + } + currentPostId = selectedPostId; + await loadVenues(); + const venuesModal = new bootstrap.Modal(document.getElementById('venuesModal')); + venuesModal.show(); + }); + + // Загрузка списка площадок + async function loadVenues() { + try { + const response = await fetch(`${PREFIX}/api/venues`); + const venues = await response.json(); + + const venuesList = document.getElementById('venues-list'); + venuesList.innerHTML = ''; + + if (venues.length === 0) { + venuesList.innerHTML = '
Площадки не найдены
'; + return; + } + + venues.forEach(venue => { + const venueItem = document.createElement('div'); + venueItem.className = 'list-group-item list-group-item-action'; + venueItem.innerHTML = ` +
+
${venue.name}
+ ${venue.event_count} событий +
+ `; + venueItem.addEventListener('click', () => { + selectedVenueName = venue.name; + loadVenueEvents(venue.name); + }); + venuesList.appendChild(venueItem); + }); + } catch (error) { + console.error('Ошибка загрузки площадок:', error); + alert('Ошибка загрузки площадок'); + } + } + + // Загрузка событий выбранной площадки + async function loadVenueEvents(venueName) { + try { + const response = await fetch(`${PREFIX}/api/venues/${encodeURIComponent(venueName)}/events?post_id=${currentPostId}`); + const events = await response.json(); + + selectedVenueEvents = events; + + // Закрываем модальное окно площадок + const venuesModal = bootstrap.Modal.getInstance(document.getElementById('venuesModal')); + venuesModal.hide(); + + // Показываем модальное окно событий + document.getElementById('selected-venue-name').textContent = venueName; + renderEventsList(events); + + const eventsModal = new bootstrap.Modal(document.getElementById('eventsModal')); + eventsModal.show(); + } catch (error) { + console.error('Ошибка загрузки событий площадки:', error); + alert('Ошибка загрузки событий площадки'); + } + } + + // Отображение списка событий с чекбоксами + function renderEventsList(events) { + const eventsList = document.getElementById('events-list'); + eventsList.innerHTML = ''; + + if (events.length === 0) { + eventsList.innerHTML = '
События не найдены
'; + return; + } + + events.forEach(event => { + const eventItem = document.createElement('div'); + eventItem.className = 'list-group-item'; + eventItem.innerHTML = ` +
+ + +
+ `; + eventsList.appendChild(eventItem); + }); + } + + // Обработчики кнопок "Выбрать все" и "Снять все" + document.getElementById('select-all-events').addEventListener('click', () => { + document.querySelectorAll('#events-list input[type="checkbox"]').forEach(checkbox => { + checkbox.checked = true; + }); + }); + + document.getElementById('deselect-all-events').addEventListener('click', () => { + document.querySelectorAll('#events-list input[type="checkbox"]').forEach(checkbox => { + checkbox.checked = false; + }); + }); + + // Обработчик кнопки "ОК" для подтверждения привязки/отвязки событий + document.getElementById('confirm-events-link').addEventListener('click', async () => { + const checkboxes = document.querySelectorAll('#events-list input[type="checkbox"]'); + const promises = []; + + for (const checkbox of checkboxes) { + const eventId = parseInt(checkbox.value); + const isChecked = checkbox.checked; + const wasLinked = selectedVenueEvents.find(e => e.id === eventId)?.linked || false; + + // Если состояние изменилось, выполняем соответствующее действие + if (isChecked && !wasLinked) { + // Привязываем событие к посту + promises.push(linkEventToPost(currentPostId, eventId)); + } else if (!isChecked && wasLinked) { + // Отвязываем событие от поста + promises.push(unlinkEventFromPost(currentPostId, eventId)); + } + } + + try { + await Promise.all(promises); + alert('Изменения сохранены'); + + // Закрываем модальное окно + const eventsModal = bootstrap.Modal.getInstance(document.getElementById('eventsModal')); + eventsModal.hide(); + + // Обновляем информацию о связях + updateInterlinkInfo(); + } catch (error) { + console.error('Ошибка при сохранении изменений:', error); + alert('Ошибка при сохранении изменений'); + } + }); + + // Функция привязки события к посту + async function linkEventToPost(postId, eventId) { + const response = await fetch(`${PREFIX}/api/interlinks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + post_id: postId, + event_id: eventId + }) + }); + + if (!response.ok) { + throw new Error('Ошибка привязки события к посту'); + } + } + + // Функция отвязки события от поста + async function unlinkEventFromPost(postId, eventId) { + const response = await fetch(`${PREFIX}/api/interlinks`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + post_id: postId, + event_id: eventId + }) + }); + + if (!response.ok) { + throw new Error('Ошибка отвязки события от поста'); + } + } }); // Убедимся, что функция доступна глобально, если потребуется вызвать из HTML window.resetLayout = resetLayout;