Добавил объединение источников. Вернул норм карту. Удалил ненужные либы

This commit is contained in:
2025-11-26 10:33:07 +03:00
parent 388753ba31
commit 609fd5a1da
10 changed files with 785 additions and 1100 deletions

View File

@@ -97,6 +97,22 @@
</button>
</div>
<!-- Add to List Button -->
<div>
<button class="btn btn-outline-success btn-sm" type="button" onclick="addSelectedToList()">
<i class="bi bi-plus-circle"></i> Добавить к
</button>
</div>
<!-- Selected Sources Counter Button -->
<div>
<button class="btn btn-outline-info btn-sm" type="button" data-bs-toggle="offcanvas"
data-bs-target="#selectedSourcesOffcanvas" aria-controls="selectedSourcesOffcanvas">
<i class="bi bi-list-check"></i> Список
<span id="selectedSourcesCounter" class="badge bg-info" style="display: none;">0</span>
</button>
</div>
<!-- Filter Toggle Button -->
<div>
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
@@ -106,23 +122,6 @@
</button>
</div>
<!-- Polygon Filter Button -->
<div>
<button class="btn btn-outline-success btn-sm" type="button"
onclick="openPolygonFilterMap()">
<i class="bi bi-pentagon"></i> Фильтр по полигону
{% if polygon_coords %}
<span class="badge bg-success"></span>
{% endif %}
</button>
{% if polygon_coords %}
<button class="btn btn-outline-danger btn-sm" type="button"
onclick="clearPolygonFilter()" title="Очистить фильтр по полигону">
<i class="bi bi-x-circle"></i>
</button>
{% endif %}
</div>
<!-- Column visibility toggle button -->
<div>
<div class="dropdown">
@@ -182,6 +181,30 @@
<input type="hidden" name="polygon" value="{{ polygon_coords }}">
{% endif %}
<!-- Polygon Filter Section -->
<div class="mb-3">
<label class="form-label fw-bold">
<i class="bi bi-pentagon"></i> Фильтр по полигону
</label>
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-success btn-sm"
onclick="openPolygonFilterMap()">
<i class="bi bi-pentagon"></i> Нарисовать полигон
{% if polygon_coords %}
<span class="badge bg-success ms-1">✓ Активен</span>
{% endif %}
</button>
{% if polygon_coords %}
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="clearPolygonFilter()" title="Очистить фильтр по полигону">
<i class="bi bi-x-circle"></i> Очистить полигон
</button>
{% endif %}
</div>
</div>
<hr class="my-3">
<!-- Satellite Selection - Multi-select -->
<div class="mb-2">
<label class="form-label">Спутник:</label>
@@ -1165,8 +1188,95 @@ function toggleColumnWithoutSave(checkbox) {
}
}
// Initialize selected sources array from localStorage
function loadSelectedSourcesFromStorage() {
try {
const storedSources = localStorage.getItem('selectedSources');
if (storedSources) {
window.selectedSources = JSON.parse(storedSources);
} else {
window.selectedSources = [];
}
} catch (e) {
console.error('Error loading selected sources from storage:', e);
window.selectedSources = [];
}
}
// Function to save selected sources to localStorage
window.saveSelectedSourcesToStorage = function () {
try {
localStorage.setItem('selectedSources', JSON.stringify(window.selectedSources));
} catch (e) {
console.error('Error saving selected sources to storage:', e);
}
}
// Function to update the selected sources counter
window.updateSelectedSourcesCounter = function () {
const counterElement = document.getElementById('selectedSourcesCounter');
if (window.selectedSources && window.selectedSources.length > 0) {
counterElement.textContent = window.selectedSources.length;
counterElement.style.display = 'inline';
} else {
counterElement.style.display = 'none';
}
}
// Function to add selected sources to the list
window.addSelectedToList = function () {
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один источник для добавления в список');
return;
}
// Get the data for each selected row and add to the selectedSources array
checkedCheckboxes.forEach(checkbox => {
const row = checkbox.closest('tr');
const sourceId = checkbox.value;
const sourceExists = window.selectedSources.some(source => source.id === sourceId);
if (!sourceExists) {
const rowData = {
id: sourceId,
name: row.cells[2].textContent.trim(),
satellite: row.cells[3].textContent.trim(),
info: row.cells[4].textContent.trim(),
ownership: row.cells[5].textContent.trim(),
coords_average: row.cells[6].textContent.trim(),
objitem_count: row.cells[7].textContent.trim()
};
window.selectedSources.push(rowData);
}
});
// Update the counter
updateSelectedSourcesCounter();
// Save selected sources to localStorage
saveSelectedSourcesToStorage();
// Clear selections in the main table
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
itemCheckboxes.forEach(checkbox => {
checkbox.checked = false;
updateRowHighlight(checkbox);
});
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
// Load selected sources from localStorage
loadSelectedSourcesFromStorage();
updateSelectedSourcesCounter();
// Setup select-all checkbox
const selectAllCheckbox = document.getElementById('select-all');
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
@@ -1227,8 +1337,221 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialize column visibility
setTimeout(initColumnVisibility, 100);
// Update the selected sources table when the offcanvas is shown
const selectedSourcesOffcanvas = document.getElementById('selectedSourcesOffcanvas');
if (selectedSourcesOffcanvas) {
selectedSourcesOffcanvas.addEventListener('show.bs.offcanvas', function () {
populateSelectedSourcesTable();
});
}
});
// Function to populate the selected sources table in the offcanvas
function populateSelectedSourcesTable() {
const tableBody = document.getElementById('selected-sources-table-body');
const noDataDiv = document.getElementById('selectedSourcesNoData');
const table = tableBody.closest('.table-responsive');
const offcanvasCounter = document.getElementById('selectedSourcesOffcanvasCounter');
if (!tableBody) return;
// Clear existing rows
tableBody.innerHTML = '';
if (!window.selectedSources || window.selectedSources.length === 0) {
// Show no data message
if (table) table.style.display = 'none';
if (noDataDiv) noDataDiv.style.display = 'block';
if (offcanvasCounter) offcanvasCounter.textContent = '0';
return;
}
// Hide no data message and show table
if (table) table.style.display = 'block';
if (noDataDiv) noDataDiv.style.display = 'none';
if (offcanvasCounter) offcanvasCounter.textContent = window.selectedSources.length;
// Add rows for each selected source
window.selectedSources.forEach((source, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="text-center">
<input type="checkbox" class="form-check-input selected-source-checkbox" value="${source.id}">
</td>
<td>${source.id}</td>
<td>${source.name}</td>
<td>${source.satellite}</td>
<td>${source.info}</td>
<td>${source.ownership}</td>
<td>${source.coords_average}</td>
<td class="text-center">${source.objitem_count}</td>
`;
tableBody.appendChild(row);
});
}
// Function to remove selected sources from the list
function removeSelectedSources() {
const checkboxes = document.querySelectorAll('#selected-sources-table-body .selected-source-checkbox:checked');
if (checkboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один источник для удаления из списка');
return;
}
// Get IDs of sources to remove
const idsToRemove = Array.from(checkboxes).map(checkbox => checkbox.value);
// Remove sources from the selectedSources array
window.selectedSources = window.selectedSources.filter(source => !idsToRemove.includes(source.id));
// Save selected sources to localStorage
saveSelectedSourcesToStorage();
// Update the counter and table
updateSelectedSourcesCounter();
populateSelectedSourcesTable();
}
// Function to show selected sources on map
function showSelectedSourcesOnMap() {
if (!window.selectedSources || window.selectedSources.length === 0) {
alert('Список источников пуст');
return;
}
const selectedIds = window.selectedSources.map(source => source.id);
const urlParams = new URLSearchParams(window.location.search);
const polygonParam = urlParams.get('polygon');
let url = '{% url "mainapp:show_sources_map" %}' + '?ids=' + selectedIds.join(',');
if (polygonParam) {
url += '&polygon=' + encodeURIComponent(polygonParam);
}
window.open(url, '_blank');
}
// Function to merge selected sources
function mergeSelectedSources() {
if (!window.selectedSources || window.selectedSources.length < 2) {
alert('Для объединения необходимо выбрать минимум 2 источника');
return;
}
// Show merge modal
const modal = new bootstrap.Modal(document.getElementById('mergeSourcesModal'));
// Populate target source info
const targetSource = window.selectedSources[0];
document.getElementById('targetSourceInfo').innerHTML = `
<strong>ID:</strong> ${targetSource.id}<br>
<strong>Имя:</strong> ${targetSource.name}<br>
<strong>Спутник:</strong> ${targetSource.satellite}<br>
<strong>Количество точек:</strong> ${targetSource.objitem_count}
`;
// Populate sources to merge list
const sourcesToMergeList = document.getElementById('sourcesToMergeList');
sourcesToMergeList.innerHTML = '';
for (let i = 1; i < window.selectedSources.length; i++) {
const source = window.selectedSources[i];
const li = document.createElement('li');
li.className = 'list-group-item';
li.innerHTML = `
<strong>ID ${source.id}:</strong> ${source.name}
<span class="badge bg-secondary">${source.objitem_count} точек</span>
`;
sourcesToMergeList.appendChild(li);
}
modal.show();
}
// Function to confirm merge
function confirmMerge() {
const infoId = document.getElementById('mergeInfoSelect').value;
const ownershipId = document.getElementById('mergeOwnershipSelect').value;
const note = document.getElementById('mergeNoteTextarea').value;
if (!infoId) {
alert('Пожалуйста, выберите тип объекта');
return;
}
if (!ownershipId) {
alert('Пожалуйста, выберите принадлежность объекта');
return;
}
// Prepare data
const sourceIds = window.selectedSources.map(source => source.id);
// Get CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Send AJAX request
fetch('{% url "mainapp:merge_sources" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({
source_ids: sourceIds,
info_id: infoId,
ownership_id: ownershipId,
note: note
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
// Clear selected sources
window.selectedSources = [];
saveSelectedSourcesToStorage();
updateSelectedSourcesCounter();
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('mergeSourcesModal'));
modal.hide();
// Reload page
location.reload();
} else {
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Произошла ошибка при объединении источников');
});
}
// Function to toggle all checkboxes in the selected sources table
function toggleAllSelectedSources(checkbox) {
const checkboxes = document.querySelectorAll('#selected-sources-table-body .selected-source-checkbox');
checkboxes.forEach(cb => {
cb.checked = checkbox.checked;
});
}
// Show source details in modal
function showSourceDetails(sourceId) {
// Update modal title
@@ -1724,6 +2047,132 @@ function showTransponderModal(transponderId) {
<!-- Include the satellite modal component -->
{% include 'mainapp/components/_satellite_modal.html' %}
<!-- Selected Sources Offcanvas -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="selectedSourcesOffcanvas" aria-labelledby="selectedSourcesOffcanvasLabel" style="width: 80%;">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="selectedSourcesOffcanvasLabel">
Выбранные источники
<span class="badge bg-info" id="selectedSourcesOffcanvasCounter">0</span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
</div>
<div class="offcanvas-body">
<div class="mb-3">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="showSelectedSourcesOnMap()">
<i class="bi bi-map"></i> Карта
</button>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="button" class="btn btn-outline-success btn-sm" onclick="mergeSelectedSources()">
<i class="bi bi-union"></i> Объединить
</button>
{% endif %}
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeSelectedSources()">
<i class="bi bi-trash"></i> Удалить из списка
</button>
</div>
</div>
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm table-bordered">
<thead class="table-light sticky-top">
<tr>
<th class="text-center" style="width: 3%;">
<input type="checkbox" id="select-all-sources" class="form-check-input" onchange="toggleAllSelectedSources(this)">
</th>
<th class="text-center" style="min-width: 60px;">ID</th>
<th style="min-width: 150px;">Имя</th>
<th style="min-width: 120px;">Спутник</th>
<th style="min-width: 120px;">Тип объекта</th>
<th style="min-width: 150px;">Принадлежность</th>
<th style="min-width: 150px;">Координаты ГЛ</th>
<th class="text-center" style="min-width: 100px;">Кол-во точек</th>
</tr>
</thead>
<tbody id="selected-sources-table-body">
<!-- Data will be loaded here via JavaScript -->
</tbody>
</table>
</div>
<div id="selectedSourcesNoData" class="text-center text-muted py-4" style="display: none;">
Список пуст. Добавьте источники из основной таблицы.
</div>
</div>
</div>
<!-- Merge Sources Modal -->
<div class="modal fade" id="mergeSourcesModal" tabindex="-1" aria-labelledby="mergeSourcesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="mergeSourcesModalLabel">
<i class="bi bi-union"></i> Объединение источников
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
<strong>Внимание:</strong> Все точки из выбранных источников будут присвоены первому источнику в списке.
Остальные источники будут удалены.
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<strong>Целевой источник (все точки будут присвоены ему):</strong>
</div>
<div class="card-body" id="targetSourceInfo">
<!-- Target source info will be populated here -->
</div>
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<strong>Источники для объединения (будут удалены):</strong>
</div>
<div class="card-body">
<ul class="list-group" id="sourcesToMergeList">
<!-- Sources to merge will be populated here -->
</ul>
</div>
</div>
<div class="mb-3">
<label for="mergeInfoSelect" class="form-label">Тип объекта <span class="text-danger">*</span></label>
<select class="form-select" id="mergeInfoSelect" required>
<option value="">Выберите тип объекта</option>
{% for info in object_infos %}
<option value="{{ info.id }}">{{ info.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="mergeOwnershipSelect" class="form-label">Принадлежность объекта <span class="text-danger">*</span></label>
<select class="form-select" id="mergeOwnershipSelect" required>
<option value="">Выберите принадлежность</option>
{% for ownership in object_ownerships %}
<option value="{{ ownership.id }}">{{ ownership.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="mergeNoteTextarea" class="form-label">Примечание</label>
<textarea class="form-control" id="mergeNoteTextarea" rows="3" placeholder="Введите примечание (необязательно)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-success" onclick="confirmMerge()">
<i class="bi bi-check-circle"></i> Объединить
</button>
</div>
</div>
</div>
</div>
<!-- Polygon Filter Map Modal -->
<div class="modal fade" id="polygonFilterModal" tabindex="-1" aria-labelledby="polygonFilterModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">