Добавил объединение источников. Вернул норм карту. Удалил ненужные либы
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user