Files
dbstorage/dbapp/mainapp/templates/mainapp/objitem_list.html

1395 lines
72 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'mainapp/base.html' %}
{% block title %}Список объектов{% endblock %}
{% block extra_css %}
<style>
.table-responsive tr.selected {
background-color: #d4edff;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12">
<h2>Список объектов</h2>
</div>
</div>
<!-- Toolbar -->
<div class="row mb-3">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<!-- Search bar made more compact -->
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
<div class="input-group">
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск..."
value="{{ search_query|default:'' }}">
<button type="button" class="btn btn-outline-primary"
onclick="performSearch()">Найти</button>
<button type="button" class="btn btn-outline-secondary"
onclick="clearSearch()">Очистить</button>
</div>
</div>
<!-- Action buttons bar -->
<div class="d-flex gap-2">
{% comment %} <button type="button" class="btn btn-success btn-sm" title="Добавить">
<i class="bi bi-plus-circle"></i> Добавить
</button>
<button type="button" class="btn btn-info btn-sm" title="Изменить">
<i class="bi bi-pencil"></i> Изменить
</button> {% endcomment %}
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
onclick="deleteSelectedObjects()">
<i class="bi bi-trash"></i> Удалить
</button>
{% endif %}
<button type="button" class="btn btn-outline-primary btn-sm" title="Показать на карте"
onclick="showSelectedOnMap()">
<i class="bi bi-map"></i> Карта
</button>
</div>
<!-- Items per page select moved here -->
<div>
<label for="items-per-page" class="form-label mb-0">Показать:</label>
<select name="items_per_page" id="items-per-page"
class="form-select form-select-sm d-inline-block" style="width: auto;"
onchange="updateItemsPerPage()">
{% for option in available_items_per_page %}
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
</div>
<!-- Filter Toggle Button -->
<div>
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters">
<i class="bi bi-funnel"></i> Фильтры
<span id="filterCounter" class="badge bg-danger" style="display: none;">0</span>
</button>
</div>
<!-- Add to List Button with Counter -->
<div>
<button class="btn btn-outline-success btn-sm" type="button" onclick="addSelectedToList()">
<i class="bi bi-plus-circle"></i> Добавить к
</button>
</div>
<!-- Selected Items Counter Button -->
<div>
<button class="btn btn-outline-info btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#selectedItemsOffcanvas" aria-controls="selectedItemsOffcanvas">
<i class="bi bi-list-check"></i> Список
<span id="selectedCounter" class="badge bg-info" style="display: none;">0</span>
</button>
</div>
<!-- Column visibility toggle button -->
<div class="dropdown">
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
id="columnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-gear"></i> Колонки
</button>
<ul class="dropdown-menu" aria-labelledby="columnVisibilityDropdown" style="z-index: 1050; max-height: 300px; overflow-y: auto;">
<li>
<label class="dropdown-item">
<input type="checkbox" id="select-all-columns" unchecked
onchange="toggleAllColumns(this)"> Выбрать всё
</label>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="0" checked
onchange="toggleColumn(this)"> Выбрать
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="1" checked
onchange="toggleColumn(this)"> Имя
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="2" checked
onchange="toggleColumn(this)"> Спутник
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="3" checked
onchange="toggleColumn(this)"> Част, МГц
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="4" checked
onchange="toggleColumn(this)"> Полоса, МГц
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="5" checked
onchange="toggleColumn(this)"> Поляризация
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="6" checked
onchange="toggleColumn(this)"> Сим. V
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="7" checked
onchange="toggleColumn(this)"> Модул
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="8" checked
onchange="toggleColumn(this)"> ОСШ
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="9" checked
onchange="toggleColumn(this)"> Время ГЛ
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="10" checked
onchange="toggleColumn(this)"> Местоположение
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="11" checked
onchange="toggleColumn(this)"> Геолокация
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="12" checked
onchange="toggleColumn(this)"> Кубсат
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="13" checked
onchange="toggleColumn(this)"> Опер. отд
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="14" checked
onchange="toggleColumn(this)"> Гео-куб, км
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="15" checked
onchange="toggleColumn(this)"> Гео-опер, км
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="16" checked
onchange="toggleColumn(this)"> Куб-опер, км
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="17" checked
onchange="toggleColumn(this)"> Обновлено
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="18" checked
onchange="toggleColumn(this)"> Кем (обновление)
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="19" checked
onchange="toggleColumn(this)"> Создано
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="20" checked
onchange="toggleColumn(this)"> Кем (создание)
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="21" unchecked
onchange="toggleColumn(this)"> Комментарий
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="22" unchecked
onchange="toggleColumn(this)"> Усреднённое
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="23" unchecked
onchange="toggleColumn(this)"> Стандарт
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="24" checked
onchange="toggleColumn(this)"> Тип источника
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="25" checked
onchange="toggleColumn(this)"> Sigma
</label>
</li>
</ul>
</div>
<!-- Pagination -->
<div class="ms-auto">
{% include 'mainapp/components/_pagination.html' with page_obj=page_obj show_info=True %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Offcanvas Filter Panel -->
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasFilters" aria-labelledby="offcanvasFiltersLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasFiltersLabel">Фильтры</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
</div>
<div class="offcanvas-body">
<form method="get" id="filter-form">
<!-- Satellite Selection - Multi-select -->
<div class="mb-2">
<label class="form-label">Спутник:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('satellite_id', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('satellite_id', false)">Снять</button>
</div>
<select name="satellite_id" class="form-select form-select-sm mb-2" multiple size="6">
{% for satellite in satellites %}
<option value="{{ satellite.id }}" {% if satellite.id in selected_satellites %}selected{% endif %}>
{{ satellite.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Frequency Filter -->
<div class="mb-2">
<label class="form-label">Частота, МГц:</label>
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1"
placeholder="От" value="{{ freq_min|default:'' }}">
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm"
placeholder="До" value="{{ freq_max|default:'' }}">
</div>
<!-- Range Filter -->
<div class="mb-2">
<label class="form-label">Полоса, МГц:</label>
<input type="number" step="0.001" name="range_min" class="form-control form-control-sm mb-1"
placeholder="От" value="{{ range_min|default:'' }}">
<input type="number" step="0.001" name="range_max" class="form-control form-control-sm"
placeholder="До" value="{{ range_max|default:'' }}">
</div>
<!-- SNR Filter -->
<div class="mb-2">
<label class="form-label">ОСШ:</label>
<input type="number" step="0.001" name="snr_min" class="form-control form-control-sm mb-1"
placeholder="От" value="{{ snr_min|default:'' }}">
<input type="number" step="0.001" name="snr_max" class="form-control form-control-sm"
placeholder="До" value="{{ snr_max|default:'' }}">
</div>
<!-- Symbol Rate Filter -->
<div class="mb-2">
<label class="form-label">Сим. v, БОД:</label>
<input type="number" step="0.001" name="bod_min" class="form-control form-control-sm mb-1"
placeholder="От" value="{{ bod_min|default:'' }}">
<input type="number" step="0.001" name="bod_max" class="form-control form-control-sm"
placeholder="До" value="{{ bod_max|default:'' }}">
</div>
<!-- Modulation Filter -->
<div class="mb-2">
<label class="form-label">Модуляция:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('modulation', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('modulation', false)">Снять</button>
</div>
<select name="modulation" class="form-select form-select-sm mb-2" multiple size="6">
{% for mod in modulations %}
<option value="{{ mod.id }}" {% if mod.id in selected_modulations %}selected{% endif %}>
{{ mod.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Polarization Filter -->
<div class="mb-2">
<label class="form-label">Поляризация:</label>
<div class="d-flex justify-content-between mb-1">
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('polarization', true)">Выбрать</button>
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="selectAllOptions('polarization', false)">Снять</button>
</div>
<select name="polarization" class="form-select form-select-sm mb-2" multiple size="4">
{% for pol in polarizations %}
<option value="{{ pol.id }}" {% if pol.id in selected_polarizations %}selected{% endif %}>
{{ pol.name }}
</option>
{% endfor %}
</select>
</div>
<!-- Kubsat Coordinates Filter -->
<div class="mb-2">
<label class="form-label">Координаты Кубсата:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_kupsat" id="has_kupsat_1"
value="1" {% if has_kupsat == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_1">Есть</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_kupsat" id="has_kupsat_0"
value="0" {% if has_kupsat == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_kupsat_0">Нет</label>
</div>
</div>
</div>
<!-- Valid Coordinates Filter -->
<div class="mb-2">
<label class="form-label">Координаты опер. отдела:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_valid" id="has_valid_1"
value="1" {% if has_valid == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_1">Есть</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_valid" id="has_valid_0"
value="0" {% if has_valid == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_valid_0">Нет</label>
</div>
</div>
</div>
<!-- Source Type Filter -->
<div class="mb-2">
<label class="form-label">Тип источника:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_source_type" id="has_source_type_1"
value="1" {% if has_source_type == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_source_type_1">Есть (ТВ)</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_source_type" id="has_source_type_0"
value="0" {% if has_source_type == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_source_type_0">Нет</label>
</div>
</div>
</div>
<!-- Sigma Filter -->
<div class="mb-2">
<label class="form-label">Sigma:</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_1"
value="1" {% if has_sigma == '1' %}checked{% endif %}>
<label class="form-check-label" for="has_sigma_1">Есть</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="has_sigma" id="has_sigma_0"
value="0" {% if has_sigma == '0' %}checked{% endif %}>
<label class="form-check-label" for="has_sigma_0">Нет</label>
</div>
</div>
</div>
<!-- Date Filter -->
<div class="mb-2">
<label class="form-label">Дата ГЛ:</label>
<div class="mb-2">
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
<button type="button" class="btn btn-outline-secondary"
onclick="setDateRange('today')">Сегодня</button>
<button type="button" class="btn btn-outline-secondary"
onclick="setDateRange('week')">Неделя</button>
</div>
<div class="btn-group btn-group-sm w-100 mb-1" role="group">
<button type="button" class="btn btn-outline-secondary"
onclick="setDateRange('month')">Месяц</button>
<button type="button" class="btn btn-outline-secondary"
onclick="setDateRange('year')">Год</button>
</div>
</div>
<input type="date" name="date_from" id="date_from" class="form-control form-control-sm mb-1"
placeholder="От" value="{{ date_from|default:'' }}">
<input type="date" name="date_to" id="date_to" class="form-control form-control-sm"
placeholder="До" value="{{ date_to|default:'' }}">
</div>
<!-- Apply Filters and Reset Buttons -->
<div class="d-grid gap-2 mt-2">
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
<a href="?" class="btn btn-secondary btn-sm">Сбросить</a>
</div>
</form>
</div>
</div>
</div>
<!-- Main Table -->
<div class="col-md">
<div class="card h-100">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
<thead class="table-dark sticky-top">
<tr>
<th scope="col" class="text-center" style="width: 3%;">
<input type="checkbox" id="select-all" class="form-check-input">
</th>
{% include 'mainapp/components/_table_header.html' with label="Имя" field="name" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Спутник" field="satellite" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Част, МГц" field="frequency" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Полоса, МГц" field="freq_range" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Поляризация" field="polarization" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Сим. V" field="bod_velocity" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Модул" field="modulation" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="ОСШ" field="snr" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Местоположение" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Геолокация" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Кубсат" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Опер. отд" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Гео-куб, км" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Гео-опер, км" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Куб-опер, км" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Обновлено" field="updated_at" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Кем(обн)" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Создано" field="created_at" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Кем(созд)" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Комментарий" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Sigma" field="" sortable=False %}
</tr>
</thead>
<tbody>
{% for item in processed_objects %}
<tr>
<td class="text-center">
<input type="checkbox" class="form-check-input item-checkbox"
value="{{ item.id }}">
</td>
<td><a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
<td>{{ item.satellite_name }}</td>
<td>{{ item.frequency }}</td>
<td>{{ item.freq_range }}</td>
<td>{{ item.polarization }}</td>
<td>{{ item.bod_velocity }}</td>
<td>{{ item.modulation }}</td>
<td>{{ item.snr }}</td>
<td>{{ item.geo_timestamp|date:"d.m.Y H:i" }}</td>
<td>{{ item.geo_location}}</td>
<td>{{ item.geo_coords }}</td>
<td>{{ item.kupsat_coords }}</td>
<td>{{ item.valid_coords }}</td>
<td>{{ item.distance_geo_kup }}</td>
<td>{{ item.distance_geo_valid }}</td>
<td>{{ item.distance_kup_valid }}</td>
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
<td>{{ item.updated_by }}</td>
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
<td>{{ item.obj.created_by }}</td>
<td>{{ item.comment }}</td>
<td>{{ item.is_average }}</td>
<td>{{ item.standard }}</td>
<td>
{% if item.obj.lyngsat_source %}
<a href="#" class="text-primary text-decoration-none" onclick="showLyngsatModal({{ item.obj.lyngsat_source.id }}); return false;">
<i class="bi bi-tv"></i> ТВ
</a>
{% else %}
-
{% endif %}
</td>
<td>
{% if item.has_sigma %}
<a href="#" class="text-info text-decoration-none" onclick="showSigmaParameterModal({{ item.obj.parameter_obj.id }}); return false;" title="{{ item.sigma_info }}">
<i class="bi bi-graph-up"></i> {{ item.sigma_info }}
</a>
{% else %}
-
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="26" class="text-center py-4">
{% if selected_satellite_id %}
Нет данных для выбранных фильтров
{% else %}
Пожалуйста, выберите спутник для отображения данных
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let lastCheckedIndex = null;
function updateRowHighlight(checkbox) {
const row = checkbox.closest('tr');
if (checkbox.checked) {
row.classList.add('selected');
} else {
row.classList.remove('selected');
}
}
function handleCheckboxClick(e) {
if (e.shiftKey && lastCheckedIndex !== null) {
const checkboxes = document.querySelectorAll('.item-checkbox');
const currentIndex = Array.from(checkboxes).indexOf(e.target);
const startIndex = Math.min(lastCheckedIndex, currentIndex);
const endIndex = Math.max(lastCheckedIndex, currentIndex);
for (let i = startIndex; i <= endIndex; i++) {
checkboxes[i].checked = e.target.checked;
updateRowHighlight(checkboxes[i]);
}
} else {
updateRowHighlight(e.target);
}
lastCheckedIndex = Array.from(document.querySelectorAll('.item-checkbox')).indexOf(e.target);
}
// Function to show selected objects on map
function showSelectedOnMap() {
// Get all checked checkboxes
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один объект для отображения на карте');
return;
}
// Extract IDs from checked checkboxes
const selectedIds = [];
checkedCheckboxes.forEach(checkbox => {
selectedIds.push(checkbox.value);
});
// Redirect to the map view with selected IDs as query parameter
const url = '{% url "mainapp:show_selected_objects_map" %}' + '?ids=' + selectedIds.join(',');
window.open(url, '_blank'); // Open in a new tab
}
function clearSelections() {
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
itemCheckboxes.forEach(checkbox => {
checkbox.checked = false;
});
// Also uncheck the select-all checkbox if it exists
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
// Remove selected class from rows
const selectedRows = document.querySelectorAll('tr.selected');
selectedRows.forEach(row => {
row.classList.remove('selected');
});
}
// Function to delete selected objects
function deleteSelectedObjects() {
// Get all checked checkboxes
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
if (checkedCheckboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один объект для удаления');
return;
}
// Confirm deletion with user
if (!confirm(`Вы уверены, что хотите удалить ${checkedCheckboxes.length} объект(ов)?`)) {
return;
}
// Extract IDs from checked checkboxes
const selectedIds = [];
checkedCheckboxes.forEach(checkbox => {
selectedIds.push(checkbox.value);
});
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();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Prepare request headers
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
// Add CSRF token to headers only if it exists
if (csrftoken) {
headers['X-CSRFToken'] = csrftoken;
}
// Send AJAX request to delete selected objects
fetch('{% url "mainapp:delete_selected_objects" %}', {
method: 'POST',
headers: headers,
body: 'ids=' + selectedIds.join(',')
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
clearSelections();
location.reload();
} else {
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Произошла ошибка при удалении объектов');
});
}
// Остальной ваш JavaScript код остается без изменений
function toggleColumn(checkbox) {
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
const table = document.querySelector('.table');
const cells = table.querySelectorAll(`td:nth-child(${columnIndex + 1}), th:nth-child(${columnIndex + 1})`);
if (checkbox.checked) {
cells.forEach(cell => {
cell.style.display = '';
});
} else {
cells.forEach(cell => {
cell.style.display = 'none';
});
}
}
function toggleAllColumns(selectAllCheckbox) {
const columnCheckboxes = document.querySelectorAll('.column-toggle');
columnCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
toggleColumn(checkbox);
});
}
document.addEventListener('DOMContentLoaded', function () {
const selectAllCheckbox = document.getElementById('select-all');
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
if (selectAllCheckbox && itemCheckboxes.length > 0) {
selectAllCheckbox.addEventListener('change', function () {
itemCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
});
itemCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function () {
const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked);
selectAllCheckbox.checked = allChecked;
});
});
// Добавляем обработчик для выбора диапазона
itemCheckboxes.forEach(checkbox => {
checkbox.addEventListener('click', handleCheckboxClick);
});
}
// Handle kubsat and valid coords checkboxes (mutually exclusive)
// Add a function to handle radio-like behavior for these checkboxes
function setupRadioLikeCheckboxes(name) {
const checkboxes = document.querySelectorAll(`input[name="${name}"]`);
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function () {
// If this checkbox is checked, uncheck the other
if (this.checked) {
checkboxes.forEach(other => {
if (other !== this) {
other.checked = false;
}
});
} else {
// If both are unchecked, no action needed
}
});
});
}
setupRadioLikeCheckboxes('has_kupsat');
setupRadioLikeCheckboxes('has_valid');
setupRadioLikeCheckboxes('has_source_type');
setupRadioLikeCheckboxes('has_sigma');
// Date range quick selection functions
window.setDateRange = function (period) {
const dateFrom = document.getElementById('date_from');
const dateTo = document.getElementById('date_to');
const today = new Date();
// Set end date to today
dateTo.valueAsDate = today;
// Calculate start date based on period
const startDate = new Date();
switch (period) {
case 'today':
startDate.setDate(today.getDate());
break;
case 'week':
startDate.setDate(today.getDate() - 7);
break;
case 'month':
startDate.setMonth(today.getMonth() - 1);
break;
case 'year':
startDate.setFullYear(today.getFullYear() - 1);
break;
}
dateFrom.valueAsDate = startDate;
};
// Function to select/deselect all options in a select element
window.selectAllOptions = function (selectName, selectAll) {
const selectElement = document.querySelector(`select[name="${selectName}"]`);
if (selectElement) {
for (let i = 0; i < selectElement.options.length; i++) {
selectElement.options[i].selected = selectAll;
}
}
};
// Get all current filter values and return as URL parameters
function getAllFilterParams() {
const form = document.getElementById('filter-form');
const searchValue = document.getElementById('toolbar-search').value;
// Create URLSearchParams object from the form
const params = new URLSearchParams(new FormData(form));
// Add search value from toolbar if present
if (searchValue.trim() !== '') {
params.set('search', searchValue);
} else {
params.delete('search');
}
return params.toString();
}
// Function to perform search
window.performSearch = function () {
const filterParams = getAllFilterParams();
window.location.search = filterParams;
};
// Function to clear search
window.clearSearch = function () {
document.getElementById('toolbar-search').value = '';
const filterParams = getAllFilterParams();
window.location.search = filterParams;
};
// Handle Enter key in toolbar search
const toolbarSearch = document.getElementById('toolbar-search');
if (toolbarSearch) {
toolbarSearch.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
performSearch();
}
});
}
//const satelliteSelect = document.querySelector('select[name="satellite_id"]');
//if (satelliteSelect) {
//satelliteSelect.addEventListener('change', function() {
// updateSatelliteSelection();
// });
//}
// Function to update items per page
window.updateItemsPerPage = function () {
const itemsPerPageSelect = document.getElementById('items-per-page');
const currentParams = new URLSearchParams(window.location.search);
// Add or update the items_per_page parameter
currentParams.set('items_per_page', itemsPerPageSelect.value);
// Remove page parameter to reset to first page when changing items per page
currentParams.delete('page');
// Update URL and reload
window.location.search = currentParams.toString();
};
// Initialize column visibility - hide creation columns by default
function initColumnVisibility() {
const creationDateCheckbox = document.querySelector('input[data-column="19"]');
const creationUserCheckbox = document.querySelector('input[data-column="20"]');
const creationDistanceGOpCheckbox = document.querySelector('input[data-column="15"]');
const creationDistanceKubOpCheckbox = document.querySelector('input[data-column="16"]');
if (creationDistanceGOpCheckbox) {
creationDistanceGOpCheckbox.checked = false;
toggleColumn(creationDistanceGOpCheckbox);
}
if (creationDistanceKubOpCheckbox) {
creationDistanceKubOpCheckbox.checked = false;
toggleColumn(creationDistanceKubOpCheckbox);
}
if (creationDateCheckbox) {
creationDateCheckbox.checked = false;
toggleColumn(creationDateCheckbox);
}
if (creationUserCheckbox) {
creationUserCheckbox.checked = false;
toggleColumn(creationUserCheckbox);
}
// Hide comment, is_average, and standard columns by default
const commentCheckbox = document.querySelector('input[data-column="21"]');
const isAverageCheckbox = document.querySelector('input[data-column="22"]');
const standardCheckbox = document.querySelector('input[data-column="23"]');
if (commentCheckbox) {
commentCheckbox.checked = false;
toggleColumn(commentCheckbox);
}
if (isAverageCheckbox) {
isAverageCheckbox.checked = false;
toggleColumn(isAverageCheckbox);
}
if (standardCheckbox) {
standardCheckbox.checked = false;
toggleColumn(standardCheckbox);
}
}
// Filter counter functionality
function updateFilterCounter() {
const form = document.getElementById('filter-form');
const formData = new FormData(form);
let filterCount = 0;
// Count non-empty form fields
for (const [key, value] of formData.entries()) {
if (value && value.trim() !== '') {
// For multi-select fields, we need to handle them separately
if (key === 'satellite_id' || key === 'modulation' || key === 'polarization') {
// Skip counting individual selections - they'll be counted as one filter
continue;
}
filterCount++;
}
}
// Count selected options in multi-select fields
const multiSelectFields = ['satellite_id', 'modulation', 'polarization'];
for (const field of multiSelectFields) {
const selectElement = document.querySelector(`select[name="${field}"]`);
if (selectElement) {
const selectedOptions = Array.from(selectElement.selectedOptions).filter(opt => opt.selected);
if (selectedOptions.length > 0) {
filterCount++; // Count the entire field as one filter even if multiple options are selected
}
}
}
// Count checkbox filters
const hasKupsatCheckboxes = document.querySelectorAll('input[name="has_kupsat"]:checked');
const hasValidCheckboxes = document.querySelectorAll('input[name="has_valid"]:checked');
if (hasKupsatCheckboxes.length > 0) {
filterCount++;
}
if (hasValidCheckboxes.length > 0) {
filterCount++;
}
// Display the filter counter
const counterElement = document.getElementById('filterCounter');
if (counterElement) {
if (filterCount > 0) {
counterElement.textContent = filterCount;
counterElement.style.display = 'inline';
} else {
counterElement.style.display = 'none';
}
}
}
// Update filter counter when page loads
updateFilterCounter();
// Add event listeners to form elements to update counter when filters change
const form = document.getElementById('filter-form');
if (form) {
// For input fields
const inputFields = form.querySelectorAll('input[type="text"], input[type="number"], input[type="date"]');
inputFields.forEach(input => {
input.addEventListener('input', updateFilterCounter);
input.addEventListener('change', updateFilterCounter);
});
// For select elements (including multi-select)
const selectFields = form.querySelectorAll('select');
selectFields.forEach(select => {
select.addEventListener('change', updateFilterCounter);
});
// For checkbox inputs
const checkboxFields = form.querySelectorAll('input[type="checkbox"]');
checkboxFields.forEach(checkbox => {
checkbox.addEventListener('change', updateFilterCounter);
});
}
// Also update when the offcanvas is shown
const offcanvasElement = document.getElementById('offcanvasFilters');
if (offcanvasElement) {
offcanvasElement.addEventListener('show.bs.offcanvas', updateFilterCounter);
}
// Initialize selected items array from localStorage
function loadSelectedItemsFromStorage() {
try {
const storedItems = localStorage.getItem('selectedItems');
if (storedItems) {
window.selectedItems = JSON.parse(storedItems);
} else {
window.selectedItems = [];
}
} catch (e) {
console.error('Error loading selected items from storage:', e);
window.selectedItems = [];
}
}
// Function to save selected items to localStorage
window.saveSelectedItemsToStorage = function() {
try {
localStorage.setItem('selectedItems', JSON.stringify(window.selectedItems));
} catch (e) {
console.error('Error saving selected items to storage:', e);
}
}
// Function to update the selected items counter
window.updateSelectedCounter = function() {
const counterElement = document.getElementById('selectedCounter');
if (window.selectedItems && window.selectedItems.length > 0) {
counterElement.textContent = window.selectedItems.length;
counterElement.style.display = 'inline';
} else {
counterElement.style.display = 'none';
}
// Also update the counter in the offcanvas
const offcanvasCounter = document.querySelector('#selectedItemsOffcanvas .offcanvas-header .badge');
if (offcanvasCounter && window.selectedItems && window.selectedItems.length > 0) {
offcanvasCounter.textContent = window.selectedItems.length;
}
}
// Load selected items from localStorage on page load
loadSelectedItemsFromStorage();
// Update counters after loading items from localStorage
updateSelectedCounter();
// Function to add selected items 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 selectedItems array
checkedCheckboxes.forEach(checkbox => {
const row = checkbox.closest('tr');
const itemId = checkbox.value;
const itemExists = window.selectedItems.some(item => item.id === itemId);
if (!itemExists) {
const rowData = {
id: itemId,
name: row.cells[1].textContent,
satellite: row.cells[2].textContent,
frequency: row.cells[3].textContent,
freq_range: row.cells[4].textContent,
polarization: row.cells[5].textContent,
bod_velocity: row.cells[6].textContent,
modulation: row.cells[7].textContent,
snr: row.cells[8].textContent,
geo_timestamp: row.cells[9].textContent,
geo_location: row.cells[10].textContent,
geo_coords: row.cells[11].textContent,
kupsat_coords: row.cells[12].textContent,
valid_coords: row.cells[13].textContent,
updated_at: row.cells[17].textContent,
updated_by: row.cells[18].textContent,
created_at: row.cells[19].textContent,
created_by: row.cells[20].textContent
};
window.selectedItems.push(rowData);
}
});
// Update the counter
if (typeof updateSelectedCounter === 'function') {
updateSelectedCounter();
}
// Save selected items to localStorage
saveSelectedItemsToStorage();
// Clear selections in the main table
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
itemCheckboxes.forEach(checkbox => {
checkbox.checked = false;
});
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
//alert(`Добавлено ${checkedCheckboxes.length} элемент(ов) в список. Всего: ${window.selectedItems.length} элемент(ов).`);
}
setTimeout(initColumnVisibility, 100);
});
// Function to populate the selected items table in the offcanvas
function populateSelectedItemsTable() {
const tableBody = document.getElementById('selected-items-table-body');
if (!tableBody) return;
// Clear existing rows
tableBody.innerHTML = '';
// Add rows for each selected item
window.selectedItems.forEach((item, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="text-center">
<input type="checkbox" class="form-check-input selected-item-checkbox" value="${item.id}">
</td>
<td>${item.name}</td>
<td>${item.satellite}</td>
<td>${item.frequency}</td>
<td>${item.freq_range}</td>
<td>${item.polarization}</td>
<td>${item.bod_velocity}</td>
<td>${item.modulation}</td>
<td>${item.snr}</td>
<td>${item.geo_timestamp}</td>
<td>${item.geo_location}</td>
<td>${item.geo_coords}</td>
<td>${item.kupsat_coords}</td>
<td>${item.valid_coords}</td>
<td>${item.updated_at}</td>
<td>${item.updated_by}</td>
<td>${item.created_at}</td>
<td>${item.created_by}</td>
`;
tableBody.appendChild(row);
});
}
// Function to remove selected items from the list
function removeSelectedItems() {
const checkboxes = document.querySelectorAll('#selected-items-table-body .selected-item-checkbox:checked');
if (checkboxes.length === 0) {
alert('Пожалуйста, выберите хотя бы один элемент для удаления из списка');
return;
}
// Get IDs of items to remove
const idsToRemove = Array.from(checkboxes).map(checkbox => checkbox.value);
// Remove items from the selectedItems array
window.selectedItems = window.selectedItems.filter(item => !idsToRemove.includes(item.id));
// Save selected items to localStorage
saveSelectedItemsToStorage();
// Update the counter and table
if (typeof updateSelectedCounter === 'function') {
updateSelectedCounter();
}
populateSelectedItemsTable();
}
// Function to send selected items (placeholder)
function sendSelectedItems() {
const selectedCount = document.querySelectorAll('#selected-items-table-body .selected-item-checkbox:checked').length;
if (selectedCount === 0) {
alert('Пожалуйста, выберите хотя бы один элемент для отправки');
return;
}
alert(`Отправка ${selectedCount} элементов... (функция в разработке)`);
// Placeholder for actual send functionality
}
// Function to toggle all checkboxes in the selected items table
function toggleAllSelected(checkbox) {
const checkboxes = document.querySelectorAll('#selected-items-table-body .selected-item-checkbox');
checkboxes.forEach(cb => {
cb.checked = checkbox.checked;
});
}
// Update the selected items table when the offcanvas is shown
document.addEventListener('DOMContentLoaded', function() {
const offcanvasElement = document.getElementById('selectedItemsOffcanvas');
if (offcanvasElement) {
offcanvasElement.addEventListener('show.bs.offcanvas', function() {
populateSelectedItemsTable();
});
}
});
</script>
<!-- Include the selected items offcanvas component -->
{% include 'mainapp/components/_selected_items_offcanvas.html' %}
<!-- Include the sigma parameter modal component -->
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
<!-- LyngSat Data Modal -->
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="lyngsatModalLabel">
<i class="bi bi-tv"></i> Данные источника LyngSat
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body" id="lyngsatModalBody">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
<script>
function showLyngsatModal(lyngsatId) {
// Показываем модальное окно
const modal = new bootstrap.Modal(document.getElementById('lyngsatModal'));
modal.show();
// Показываем индикатор загрузки
const modalBody = document.getElementById('lyngsatModalBody');
modalBody.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
`;
// Загружаем данные
fetch(`/api/lyngsat/${lyngsatId}/`)
.then(response => {
if (!response.ok) {
throw new Error('Ошибка загрузки данных');
}
return response.json();
})
.then(data => {
// Формируем HTML с данными
let html = `
<div class="container-fluid">
<div class="row g-3">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<strong><i class="bi bi-info-circle"></i> Основная информация</strong>
</div>
<div class="card-body">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted" style="width: 40%;">Спутник:</td>
<td><strong>${data.satellite}</strong></td>
</tr>
<tr>
<td class="text-muted">Частота:</td>
<td><strong>${data.frequency} МГц</strong></td>
</tr>
<tr>
<td class="text-muted">Поляризация:</td>
<td><span class="badge bg-info">${data.polarization}</span></td>
</tr>
<tr>
<td class="text-muted">Канал:</td>
<td>${data.channel_info}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<strong><i class="bi bi-gear"></i> Технические параметры</strong>
</div>
<div class="card-body">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted" style="width: 40%;">Модуляция:</td>
<td><span class="badge bg-secondary">${data.modulation}</span></td>
</tr>
<tr>
<td class="text-muted">Стандарт:</td>
<td><span class="badge bg-secondary">${data.standard}</span></td>
</tr>
<tr>
<td class="text-muted">Сим. скорость:</td>
<td><strong>${data.sym_velocity} БОД</strong></td>
</tr>
<tr>
<td class="text-muted">FEC:</td>
<td>${data.fec}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<strong><i class="bi bi-clock-history"></i> Дополнительная информация</strong>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-2">
<span class="text-muted">Последнее обновление:</span><br>
<strong>${data.last_update}</strong>
</p>
</div>
<div class="col-md-6">
${data.url ? `
<p class="mb-2">
<span class="text-muted">Ссылка на источник:</span><br>
<a href="${data.url}" target="_blank" class="btn btn-sm btn-outline-primary">
<i class="bi bi-link-45deg"></i> Открыть на LyngSat
</a>
</p>
` : ''}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
modalBody.innerHTML = html;
})
.catch(error => {
modalBody.innerHTML = `
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle"></i> ${error.message}
</div>
`;
});
}
</script>
{% endblock %}