790 lines
40 KiB
HTML
790 lines
40 KiB
HTML
{% extends 'mainapp/base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Кубсат{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid px-3">
|
||
<div class="row mb-3">
|
||
<div class="col-12">
|
||
<h2>Кубсат</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Форма фильтров -->
|
||
<form method="get" id="filterForm" class="mb-4">
|
||
{% csrf_token %}
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Фильтры</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<!-- Спутники -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.satellites.id_for_label }}" class="form-label">{{ form.satellites.label }}</label>
|
||
<div class="d-flex justify-content-between mb-1">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('satellites', true)">Выбрать</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('satellites', false)">Снять</button>
|
||
</div>
|
||
{{ form.satellites }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
|
||
<!-- Полоса спутника -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.band.id_for_label }}" class="form-label">{{ form.band.label }}</label>
|
||
<div class="d-flex justify-content-between mb-1">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('band', true)">Выбрать</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('band', false)">Снять</button>
|
||
</div>
|
||
{{ form.band }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
|
||
<!-- Поляризация -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.polarization.id_for_label }}" class="form-label">{{ form.polarization.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>
|
||
{{ form.polarization }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
|
||
<!-- Модуляция -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.modulation.id_for_label }}" class="form-label">{{ form.modulation.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>
|
||
{{ form.modulation }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Центральная частота -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">Центральная частота (МГц)</label>
|
||
<div class="input-group">
|
||
{{ form.frequency_min }}
|
||
<span class="input-group-text">—</span>
|
||
{{ form.frequency_max }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Полоса -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">Полоса (МГц)</label>
|
||
<div class="input-group">
|
||
{{ form.freq_range_min }}
|
||
<span class="input-group-text">—</span>
|
||
{{ form.freq_range_max }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Тип объекта -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.object_type.id_for_label }}" class="form-label">{{ form.object_type.label }}</label>
|
||
<div class="d-flex justify-content-between mb-1">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('object_type', true)">Выбрать</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('object_type', false)">Снять</button>
|
||
</div>
|
||
{{ form.object_type }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
|
||
<!-- Принадлежность объекта -->
|
||
<div class="col-md-3 mb-3">
|
||
<label for="{{ form.object_ownership.id_for_label }}" class="form-label">{{ form.object_ownership.label }}</label>
|
||
<div class="d-flex justify-content-between mb-1">
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('object_ownership', true)">Выбрать</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||
onclick="selectAllOptions('object_ownership', false)">Снять</button>
|
||
</div>
|
||
{{ form.object_ownership }}
|
||
<small class="form-text text-muted">Удерживайте Ctrl для выбора нескольких</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Количество ObjItem -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">{{ form.objitem_count.label }}</label>
|
||
<div>
|
||
{% for radio in form.objitem_count %}
|
||
<div class="form-check">
|
||
{{ radio.tag }}
|
||
<label class="form-check-label" for="{{ radio.id_for_label }}">
|
||
{{ radio.choice_label }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Планы на (фиктивный) -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">{{ form.has_plans.label }} (не работает)</label>
|
||
<div>
|
||
{% for radio in form.has_plans %}
|
||
<div class="form-check">
|
||
{{ radio.tag }}
|
||
<label class="form-check-label" for="{{ radio.id_for_label }}">
|
||
{{ radio.choice_label }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Успех 1 (фиктивный) -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">{{ form.success_1.label }} (не работает)</label>
|
||
<div>
|
||
{% for radio in form.success_1 %}
|
||
<div class="form-check">
|
||
{{ radio.tag }}
|
||
<label class="form-check-label" for="{{ radio.id_for_label }}">
|
||
{{ radio.choice_label }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Успех 2 (фиктивный) -->
|
||
<div class="col-md-3 mb-3">
|
||
<label class="form-label">{{ form.success_2.label }} (не работает)</label>
|
||
<div>
|
||
{% for radio in form.success_2 %}
|
||
<div class="form-check">
|
||
{{ radio.tag }}
|
||
<label class="form-check-label" for="{{ radio.id_for_label }}">
|
||
{{ radio.choice_label }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Диапазон дат (фиктивный) -->
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label">Диапазон дат ГЛ:</label>
|
||
<div class="input-group">
|
||
{{ form.date_from }}
|
||
<span class="input-group-text">—</span>
|
||
{{ form.date_to }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<button type="submit" class="btn btn-primary">Применить фильтры</button>
|
||
<a href="{% url 'mainapp:kubsat' %}" class="btn btn-secondary">Сбросить</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- Кнопка экспорта и статистика -->
|
||
{% if sources_with_date_info %}
|
||
<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">
|
||
<!-- Поиск по имени точки -->
|
||
<div class="input-group" style="max-width: 350px;">
|
||
<input type="text" id="searchObjitemName" class="form-control"
|
||
placeholder="Поиск по имени точки..."
|
||
oninput="filterTableByName()">
|
||
<button type="button" class="btn btn-outline-secondary" onclick="clearSearch()">
|
||
<i class="bi bi-x-lg"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<button type="button" class="btn btn-success" onclick="exportToExcel()">
|
||
<i class="bi bi-file-earmark-excel"></i> Экспорт в Excel
|
||
</button>
|
||
<span class="text-muted" id="statsCounter">
|
||
Найдено объектов: {{ sources_with_date_info|length }},
|
||
точек: {% for source_data in sources_with_date_info %}{{ source_data.objitems_data|length }}{% if not forloop.last %}+{% endif %}{% endfor %}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Таблица результатов -->
|
||
{% if sources_with_date_info %}
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card">
|
||
<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 table-bordered" style="font-size: 0.85rem;" id="resultsTable">
|
||
<thead class="table-dark sticky-top">
|
||
<tr>
|
||
<th style="min-width: 80px;">ID объекта</th>
|
||
<th style="min-width: 120px;">Тип объекта</th>
|
||
<th style="min-width: 150px;">Принадлежность объекта</th>
|
||
<th class="text-center" style="min-width: 100px;">Кол-во точек</th>
|
||
<th style="min-width: 120px;">Имя точки</th>
|
||
<th style="min-width: 150px;">Спутник</th>
|
||
<th style="min-width: 100px;">Частота (МГц)</th>
|
||
<th style="min-width: 100px;">Полоса (МГц)</th>
|
||
<th style="min-width: 100px;">Поляризация</th>
|
||
<th style="min-width: 100px;">Модуляция</th>
|
||
<th style="min-width: 150px;">Координаты ГЛ</th>
|
||
<th style="min-width: 100px;">Дата ГЛ</th>
|
||
<th style="min-width: 150px;">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for source_data in sources_with_date_info %}
|
||
{% for objitem_data in source_data.objitems_data %}
|
||
<tr data-source-id="{{ source_data.source.id }}"
|
||
data-objitem-id="{{ objitem_data.objitem.id }}"
|
||
data-objitem-name="{{ objitem_data.objitem.name|default:'' }}"
|
||
data-matches-date="{{ objitem_data.matches_date|yesno:'true,false' }}"
|
||
data-is-first-in-source="{% if forloop.first %}true{% else %}false{% endif %}">
|
||
|
||
<!-- ID Source (только для первой строки источника) -->
|
||
{% if forloop.first %}
|
||
<td rowspan="{{ source_data.objitems_data|length }}" class="source-id-cell">{{ source_data.source.id }}</td>
|
||
{% endif %}
|
||
|
||
<!-- Тип объекта (только для первой строки источника) -->
|
||
{% if forloop.first %}
|
||
<td rowspan="{{ source_data.objitems_data|length }}" class="source-type-cell">{{ source_data.source.info.name|default:"-" }}</td>
|
||
{% endif %}
|
||
|
||
<!-- Принадлежность объекта (только для первой строки источника) -->
|
||
{% if forloop.first %}
|
||
<td rowspan="{{ source_data.objitems_data|length }}" class="source-ownership-cell">
|
||
{% if source_data.source.ownership %}
|
||
{% if source_data.source.ownership.name == "ТВ" and source_data.has_lyngsat %}
|
||
<a href="#" class="text-primary text-decoration-none"
|
||
onclick="showLyngsatModal({{ source_data.lyngsat_id }}); return false;">
|
||
<i class="bi bi-tv"></i> {{ source_data.source.ownership.name }}
|
||
</a>
|
||
{% else %}
|
||
{{ source_data.source.ownership.name }}
|
||
{% endif %}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
|
||
<!-- Количество точек (только для первой строки источника) -->
|
||
{% if forloop.first %}
|
||
<td rowspan="{{ source_data.objitems_data|length }}" class="text-center source-count-cell" data-initial-count="{{ source_data.objitems_data|length }}">{{ source_data.objitems_data|length }}</td>
|
||
{% endif %}
|
||
|
||
<!-- Имя точки -->
|
||
<td>{{ objitem_data.objitem.name|default:"-" }}</td>
|
||
|
||
<!-- Спутник -->
|
||
<td>
|
||
{% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.id_satellite %}
|
||
{{ objitem_data.objitem.parameter_obj.id_satellite.name }}
|
||
{% if objitem_data.objitem.parameter_obj.id_satellite.norad %}
|
||
({{ objitem_data.objitem.parameter_obj.id_satellite.norad }})
|
||
{% endif %}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Частота -->
|
||
<td>
|
||
{% if objitem_data.objitem.parameter_obj %}
|
||
{{ objitem_data.objitem.parameter_obj.frequency|default:"-"|floatformat:3 }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Полоса -->
|
||
<td>
|
||
{% if objitem_data.objitem.parameter_obj %}
|
||
{{ objitem_data.objitem.parameter_obj.freq_range|default:"-"|floatformat:3 }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Поляризация -->
|
||
<td>
|
||
{% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.polarization %}
|
||
{{ objitem_data.objitem.parameter_obj.polarization.name }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Модуляция -->
|
||
<td>
|
||
{% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.modulation %}
|
||
{{ objitem_data.objitem.parameter_obj.modulation.name }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Координаты ГЛ -->
|
||
<td>
|
||
{% if objitem_data.objitem.geo_obj and objitem_data.objitem.geo_obj.coords %}
|
||
{{ objitem_data.objitem.geo_obj.coords.y }}, {{ objitem_data.objitem.geo_obj.coords.x }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Дата ГЛ -->
|
||
<td>
|
||
{% if objitem_data.geo_date %}
|
||
{{ objitem_data.geo_date|date:"d.m.Y" }}
|
||
{% else %}
|
||
-
|
||
{% endif %}
|
||
</td>
|
||
|
||
<!-- Действия -->
|
||
<td>
|
||
<div class="btn-group" role="group">
|
||
<button type="button" class="btn btn-sm btn-danger" onclick="removeObjItem(this)" title="Удалить точку">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
{% if forloop.first %}
|
||
<button type="button" class="btn btn-sm btn-warning" onclick="removeSource(this)" title="Удалить весь объект">
|
||
<i class="bi bi-trash-fill"></i>
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% elif request.GET %}
|
||
<div class="alert alert-info">
|
||
По заданным критериям ничего не найдено.
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<script>
|
||
function removeObjItem(button) {
|
||
// Удаляем строку точки из таблицы (не из базы данных)
|
||
const row = button.closest('tr');
|
||
const sourceId = row.dataset.sourceId;
|
||
const isFirstInSource = row.dataset.isFirstInSource === 'true';
|
||
|
||
// Получаем все строки этого источника
|
||
const sourceRows = Array.from(document.querySelectorAll(`tr[data-source-id="${sourceId}"]`));
|
||
|
||
if (sourceRows.length === 1) {
|
||
// Последняя строка источника - просто удаляем
|
||
row.remove();
|
||
} else if (isFirstInSource) {
|
||
// Удаляем первую строку - нужно перенести ячейки с rowspan на следующую строку
|
||
const nextRow = sourceRows[1];
|
||
|
||
// Находим ячейки с rowspan (ID Source, Тип объекта, Количество точек)
|
||
const sourceIdCell = row.querySelector('.source-id-cell');
|
||
const sourceTypeCell = row.querySelector('.source-type-cell');
|
||
const sourceCountCell = row.querySelector('.source-count-cell');
|
||
|
||
if (sourceIdCell && sourceTypeCell && sourceCountCell) {
|
||
const currentRowspan = parseInt(sourceIdCell.getAttribute('rowspan'));
|
||
const newRowspan = currentRowspan - 1;
|
||
|
||
// Создаем новые ячейки для следующей строки
|
||
const newSourceIdCell = sourceIdCell.cloneNode(true);
|
||
const newSourceTypeCell = sourceTypeCell.cloneNode(true);
|
||
const newSourceCountCell = sourceCountCell.cloneNode(true);
|
||
|
||
newSourceIdCell.setAttribute('rowspan', newRowspan);
|
||
newSourceTypeCell.setAttribute('rowspan', newRowspan);
|
||
newSourceCountCell.setAttribute('rowspan', newRowspan);
|
||
|
||
// Обновляем счетчик точек
|
||
newSourceCountCell.textContent = newRowspan;
|
||
|
||
// Вставляем ячейки в начало следующей строки
|
||
nextRow.insertBefore(newSourceCountCell, nextRow.firstChild);
|
||
nextRow.insertBefore(newSourceTypeCell, nextRow.firstChild);
|
||
nextRow.insertBefore(newSourceIdCell, nextRow.firstChild);
|
||
|
||
// Переносим кнопку "Удалить объект" на следующую строку
|
||
const actionsCell = nextRow.querySelector('td:last-child');
|
||
if (actionsCell) {
|
||
const btnGroup = actionsCell.querySelector('.btn-group');
|
||
if (btnGroup && btnGroup.children.length === 1) {
|
||
// Добавляем кнопку удаления объекта
|
||
const deleteSourceBtn = document.createElement('button');
|
||
deleteSourceBtn.type = 'button';
|
||
deleteSourceBtn.className = 'btn btn-sm btn-warning';
|
||
deleteSourceBtn.onclick = function() { removeSource(this); };
|
||
deleteSourceBtn.title = 'Удалить весь объект';
|
||
deleteSourceBtn.innerHTML = '<i class="bi bi-trash-fill"></i>';
|
||
btnGroup.appendChild(deleteSourceBtn);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Обновляем data-is-first-in-source для следующей строки
|
||
nextRow.dataset.isFirstInSource = 'true';
|
||
|
||
// Удаляем текущую строку
|
||
row.remove();
|
||
} else {
|
||
// Удаляем не первую строку - уменьшаем rowspan в первой строке
|
||
const firstRow = sourceRows[0];
|
||
const sourceIdCell = firstRow.querySelector('.source-id-cell');
|
||
const sourceTypeCell = firstRow.querySelector('.source-type-cell');
|
||
const sourceCountCell = firstRow.querySelector('.source-count-cell');
|
||
|
||
if (sourceIdCell && sourceTypeCell && sourceCountCell) {
|
||
const currentRowspan = parseInt(sourceIdCell.getAttribute('rowspan'));
|
||
const newRowspan = currentRowspan - 1;
|
||
|
||
sourceIdCell.setAttribute('rowspan', newRowspan);
|
||
sourceTypeCell.setAttribute('rowspan', newRowspan);
|
||
sourceCountCell.setAttribute('rowspan', newRowspan);
|
||
|
||
// Обновляем счетчик точек
|
||
sourceCountCell.textContent = newRowspan;
|
||
}
|
||
|
||
// Удаляем текущую строку
|
||
row.remove();
|
||
}
|
||
|
||
// Обновляем общий счетчик
|
||
updateCounter();
|
||
}
|
||
|
||
function removeSource(button) {
|
||
// Удаляем все строки источника из таблицы (не из базы данных)
|
||
const row = button.closest('tr');
|
||
const sourceId = row.dataset.sourceId;
|
||
|
||
// Находим все строки с этим source_id и удаляем их
|
||
const rows = document.querySelectorAll(`tr[data-source-id="${sourceId}"]`);
|
||
rows.forEach(r => r.remove());
|
||
|
||
// Обновляем счетчик
|
||
updateCounter();
|
||
}
|
||
|
||
function updateCounter() {
|
||
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||
const counter = document.getElementById('statsCounter');
|
||
if (counter) {
|
||
// Подсчитываем уникальные источники и точки (только видимые)
|
||
const uniqueSources = new Set();
|
||
let visibleRowsCount = 0;
|
||
rows.forEach(row => {
|
||
if (row.style.display !== 'none') {
|
||
uniqueSources.add(row.dataset.sourceId);
|
||
visibleRowsCount++;
|
||
}
|
||
});
|
||
counter.textContent = `Найдено объектов: ${uniqueSources.size}, точек: ${visibleRowsCount}`;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
function exportToExcel() {
|
||
// Собираем ID оставшихся в таблице точек (ObjItem)
|
||
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||
const objitemIds = Array.from(rows).map(row => row.dataset.objitemId);
|
||
|
||
if (objitemIds.length === 0) {
|
||
alert('Нет данных для экспорта');
|
||
return;
|
||
}
|
||
|
||
// Создаем форму для отправки
|
||
const form = document.createElement('form');
|
||
form.method = 'POST';
|
||
form.action = '{% url "mainapp:kubsat_export" %}';
|
||
|
||
// Добавляем CSRF токен
|
||
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]');
|
||
if (csrfToken) {
|
||
const csrfInput = document.createElement('input');
|
||
csrfInput.type = 'hidden';
|
||
csrfInput.name = 'csrfmiddlewaretoken';
|
||
csrfInput.value = csrfToken.value;
|
||
form.appendChild(csrfInput);
|
||
}
|
||
|
||
// Добавляем ID точек
|
||
objitemIds.forEach(id => {
|
||
const input = document.createElement('input');
|
||
input.type = 'hidden';
|
||
input.name = 'objitem_ids';
|
||
input.value = id;
|
||
form.appendChild(input);
|
||
});
|
||
|
||
// Отправляем форму
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
document.body.removeChild(form);
|
||
}
|
||
|
||
// Функция для выбора/снятия всех опций в select
|
||
function selectAllOptions(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;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Фильтрация таблицы по имени точки
|
||
function filterTableByName() {
|
||
const searchValue = document.getElementById('searchObjitemName').value.toLowerCase().trim();
|
||
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||
|
||
if (!searchValue) {
|
||
// Показываем все строки
|
||
rows.forEach(row => {
|
||
row.style.display = '';
|
||
});
|
||
// Восстанавливаем rowspan
|
||
recalculateRowspans();
|
||
updateCounter();
|
||
return;
|
||
}
|
||
|
||
// Группируем строки по source_id
|
||
const sourceGroups = {};
|
||
rows.forEach(row => {
|
||
const sourceId = row.dataset.sourceId;
|
||
if (!sourceGroups[sourceId]) {
|
||
sourceGroups[sourceId] = [];
|
||
}
|
||
sourceGroups[sourceId].push(row);
|
||
});
|
||
|
||
// Фильтруем по имени точки используя data-атрибут
|
||
Object.keys(sourceGroups).forEach(sourceId => {
|
||
const sourceRows = sourceGroups[sourceId];
|
||
let hasVisibleRows = false;
|
||
|
||
sourceRows.forEach(row => {
|
||
// Используем data-атрибут для получения имени точки
|
||
const name = (row.dataset.objitemName || '').toLowerCase();
|
||
|
||
if (name.includes(searchValue)) {
|
||
row.style.display = '';
|
||
hasVisibleRows = true;
|
||
} else {
|
||
row.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Если нет видимых строк в группе, скрываем все (включая ячейки с rowspan)
|
||
if (!hasVisibleRows) {
|
||
sourceRows.forEach(row => {
|
||
row.style.display = 'none';
|
||
});
|
||
}
|
||
});
|
||
|
||
// Пересчитываем rowspan для видимых строк
|
||
recalculateRowspans();
|
||
updateCounter();
|
||
}
|
||
|
||
// Пересчет rowspan для видимых строк
|
||
function recalculateRowspans() {
|
||
const rows = document.querySelectorAll('#resultsTable tbody tr');
|
||
|
||
// Группируем видимые строки по source_id
|
||
const sourceGroups = {};
|
||
rows.forEach(row => {
|
||
if (row.style.display !== 'none') {
|
||
const sourceId = row.dataset.sourceId;
|
||
if (!sourceGroups[sourceId]) {
|
||
sourceGroups[sourceId] = [];
|
||
}
|
||
sourceGroups[sourceId].push(row);
|
||
}
|
||
});
|
||
|
||
// Обновляем rowspan для каждой группы
|
||
Object.keys(sourceGroups).forEach(sourceId => {
|
||
const visibleRows = sourceGroups[sourceId];
|
||
const newRowspan = visibleRows.length;
|
||
|
||
if (visibleRows.length > 0) {
|
||
const firstRow = visibleRows[0];
|
||
const sourceIdCell = firstRow.querySelector('.source-id-cell');
|
||
const sourceTypeCell = firstRow.querySelector('.source-type-cell');
|
||
const sourceOwnershipCell = firstRow.querySelector('.source-ownership-cell');
|
||
const sourceCountCell = firstRow.querySelector('.source-count-cell');
|
||
|
||
if (sourceIdCell) sourceIdCell.setAttribute('rowspan', newRowspan);
|
||
if (sourceTypeCell) sourceTypeCell.setAttribute('rowspan', newRowspan);
|
||
if (sourceOwnershipCell) sourceOwnershipCell.setAttribute('rowspan', newRowspan);
|
||
if (sourceCountCell) {
|
||
sourceCountCell.setAttribute('rowspan', newRowspan);
|
||
// Обновляем отображаемое количество точек
|
||
sourceCountCell.textContent = newRowspan;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Очистка поиска
|
||
function clearSearch() {
|
||
document.getElementById('searchObjitemName').value = '';
|
||
filterTableByName();
|
||
}
|
||
|
||
// Обновляем счетчик при загрузке страницы
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
updateCounter();
|
||
});
|
||
|
||
// Функция для показа модального окна LyngSat
|
||
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 => {
|
||
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>
|
||
|
||
<style>
|
||
.table th {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.badge {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.form-check {
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
|
||
|
||
/* Стили для кнопок действий */
|
||
.btn-sm {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
/* Sticky header */
|
||
.sticky-top {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
</style>
|
||
|
||
<!-- 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>
|
||
{% endblock %}
|