392 lines
16 KiB
HTML
392 lines
16 KiB
HTML
{% extends "mainapp/base.html" %}
|
||
{% load static %}
|
||
|
||
{% block title %}Наличие сигнала объектов{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.marks-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.marks-table th,
|
||
.marks-table td {
|
||
border: 1px solid #dee2e6;
|
||
padding: 8px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.marks-table th {
|
||
background-color: #f8f9fa;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
}
|
||
|
||
.source-info-cell {
|
||
min-width: 250px;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.marks-cell {
|
||
min-width: 150px;
|
||
text-align: center;
|
||
}
|
||
|
||
.actions-cell {
|
||
min-width: 180px;
|
||
text-align: center;
|
||
}
|
||
|
||
.mark-status {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.mark-present {
|
||
color: #28a745;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.mark-absent {
|
||
color: #dc3545;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn-mark {
|
||
padding: 6px 16px;
|
||
font-size: 0.875rem;
|
||
min-width: 100px;
|
||
}
|
||
|
||
.btn-edit-mark {
|
||
padding: 2px 8px;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.filter-section {
|
||
background: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.no-marks {
|
||
color: #6c757d;
|
||
font-style: italic;
|
||
text-align: center;
|
||
}
|
||
|
||
.btn-mark:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn-edit-mark:disabled {
|
||
opacity: 0.5;
|
||
cursor: wait;
|
||
}
|
||
|
||
.mark-status {
|
||
transition: color 0.3s ease;
|
||
}
|
||
|
||
.btn-edit-mark:hover:not(:disabled) {
|
||
background-color: #6c757d;
|
||
color: white;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid mt-4">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2>Наличие сигнала объектов</h2>
|
||
</div>
|
||
|
||
<!-- Фильтры -->
|
||
<div class="filter-section">
|
||
<form method="get" class="row g-3">
|
||
<div class="col-md-4">
|
||
<label for="search" class="form-label">Поиск по имени объекта</label>
|
||
<input type="text" class="form-control" id="search" name="search"
|
||
placeholder="Введите имя объекта..."
|
||
value="{{ request.GET.search|default:'' }}">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label for="satellite" class="form-label">Спутник</label>
|
||
<select class="form-select" id="satellite" name="satellite">
|
||
<option value="">Все спутники</option>
|
||
{% for sat in satellites %}
|
||
<option value="{{ sat.id }}" {% if request.GET.satellite == sat.id|stringformat:"s" %}selected{% endif %}>
|
||
{{ sat.name }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4 d-flex align-items-end">
|
||
<button type="submit" class="btn btn-primary me-2">Применить</button>
|
||
<a href="{% url 'mainapp:object_marks' %}" class="btn btn-secondary">Сбросить</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Таблица с наличие сигналами -->
|
||
<div class="table-responsive">
|
||
<table class="marks-table table table-bordered">
|
||
<thead>
|
||
<tr>
|
||
<th class="source-info-cell">Информация об объекте</th>
|
||
<th class="marks-cell">Наличие</th>
|
||
<th class="marks-cell">Дата и время</th>
|
||
<th class="actions-cell">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for source in sources %}
|
||
{% with marks=source.marks.all %}
|
||
{% if marks %}
|
||
<!-- Первая строка с информацией об объекте и первой отметкой -->
|
||
<tr data-source-id="{{ source.id }}">
|
||
<td class="source-info-cell" rowspan="{{ marks.count }}">
|
||
<div><strong>ID:</strong> {{ source.id }}</div>
|
||
<div><strong>Имя объекта:</strong> {{ source.objitem_name }}</div>
|
||
<div><strong>Дата создания:</strong> {{ source.created_at|date:"d.m.Y H:i" }}</div>
|
||
<div><strong>Кол-во объектов:</strong> {{ source.source_objitems.count }}</div>
|
||
{% if source.coords_average %}
|
||
<div><strong>Усреднённые координаты:</strong> Есть</div>
|
||
{% endif %}
|
||
{% if source.coords_kupsat %}
|
||
<div><strong>Координаты Кубсата:</strong> Есть</div>
|
||
{% endif %}
|
||
{% if source.coords_valid %}
|
||
<div><strong>Координаты оперативников:</strong> Есть</div>
|
||
{% endif %}
|
||
</td>
|
||
{% with first_mark=marks.0 %}
|
||
<td class="marks-cell" data-mark-id="{{ first_mark.id }}">
|
||
<span class="mark-status {% if first_mark.mark %}mark-present{% else %}mark-absent{% endif %}">
|
||
{% if first_mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
|
||
</span>
|
||
{% if first_mark.can_edit %}
|
||
<button class="btn btn-sm btn-outline-secondary btn-edit-mark ms-2"
|
||
onclick="toggleMark({{ first_mark.id }}, {{ first_mark.mark|yesno:'true,false' }})">
|
||
✎
|
||
</button>
|
||
{% endif %}
|
||
</td>
|
||
<td class="marks-cell">
|
||
<div>{{ first_mark.timestamp|date:"d.m.Y H:i" }}</div>
|
||
<small class="text-muted">{{ first_mark.created_by|default:"—" }}</small>
|
||
</td>
|
||
<td class="actions-cell" rowspan="{{ marks.count }}">
|
||
<div class="action-buttons" id="actions-{{ source.id }}">
|
||
<button class="btn btn-success btn-mark btn-sm"
|
||
onclick="addMark({{ source.id }}, true)">
|
||
✓ Есть
|
||
</button>
|
||
<button class="btn btn-danger btn-mark btn-sm"
|
||
onclick="addMark({{ source.id }}, false)">
|
||
✗ Нет
|
||
</button>
|
||
</div>
|
||
</td>
|
||
{% endwith %}
|
||
</tr>
|
||
|
||
<!-- Остальные наличие сигнала -->
|
||
{% for mark in marks|slice:"1:" %}
|
||
<tr data-source-id="{{ source.id }}">
|
||
<td class="marks-cell" data-mark-id="{{ mark.id }}">
|
||
<span class="mark-status {% if mark.mark %}mark-present{% else %}mark-absent{% endif %}">
|
||
{% if mark.mark %}✓ Есть{% else %}✗ Нет{% endif %}
|
||
</span>
|
||
{% if mark.can_edit %}
|
||
<button class="btn btn-sm btn-outline-secondary btn-edit-mark ms-2"
|
||
onclick="toggleMark({{ mark.id }}, {{ mark.mark|yesno:'true,false' }})">
|
||
✎
|
||
</button>
|
||
{% endif %}
|
||
</td>
|
||
<td class="marks-cell">
|
||
<div>{{ mark.timestamp|date:"d.m.Y H:i" }}</div>
|
||
<small class="text-muted">{{ mark.created_by|default:"—" }}</small>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% else %}
|
||
<!-- Объект без отметок -->
|
||
<tr data-source-id="{{ source.id }}">
|
||
<td class="source-info-cell">
|
||
<div><strong>ID:</strong> {{ source.id }}</div>
|
||
<div><strong>Имя объекта:</strong> {{ source.objitem_name }}</div>
|
||
<div><strong>Дата создания:</strong> {{ source.created_at|date:"d.m.Y H:i" }}</div>
|
||
<div><strong>Кол-во объектов:</strong> {{ source.source_objitems.count }}</div>
|
||
{% if source.coords_average %}
|
||
<div><strong>Усреднённые координаты:</strong> Есть</div>
|
||
{% endif %}
|
||
{% if source.coords_kupsat %}
|
||
<div><strong>Координаты Кубсата:</strong> Есть</div>
|
||
{% endif %}
|
||
{% if source.coords_valid %}
|
||
<div><strong>Координаты оперативников:</strong> Есть</div>
|
||
{% endif %}
|
||
</td>
|
||
<td colspan="2" class="no-marks">Отметок нет</td>
|
||
<td class="actions-cell">
|
||
<div class="action-buttons" id="actions-{{ source.id }}">
|
||
<button class="btn btn-success btn-mark btn-sm"
|
||
onclick="addMark({{ source.id }}, true)">
|
||
✓ Есть
|
||
</button>
|
||
<button class="btn btn-danger btn-mark btn-sm"
|
||
onclick="addMark({{ source.id }}, false)">
|
||
✗ Нет
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% endwith %}
|
||
{% empty %}
|
||
<tr>
|
||
<td colspan="4" class="text-center py-4">
|
||
<p class="text-muted mb-0">Объекти не найдены</p>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Пагинация -->
|
||
{% if is_paginated %}
|
||
<nav aria-label="Навигация по страницам" class="mt-4">
|
||
<ul class="pagination justify-content-center">
|
||
{% if page_obj.has_previous %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="?page=1{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Первая</a>
|
||
</li>
|
||
<li class="page-item">
|
||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Предыдущая</a>
|
||
</li>
|
||
{% endif %}
|
||
|
||
<li class="page-item active">
|
||
<span class="page-link">{{ page_obj.number }} из {{ page_obj.paginator.num_pages }}</span>
|
||
</li>
|
||
|
||
{% if page_obj.has_next %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Следующая</a>
|
||
</li>
|
||
<li class="page-item">
|
||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.satellite %}&satellite={{ request.GET.satellite }}{% endif %}">Последняя</a>
|
||
</li>
|
||
{% endif %}
|
||
</ul>
|
||
</nav>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
function addMark(sourceId, mark) {
|
||
// Отключить кнопки для этого объекта
|
||
const buttons = document.querySelectorAll(`#actions-${sourceId} button`);
|
||
buttons.forEach(btn => btn.disabled = true);
|
||
|
||
fetch("{% url 'mainapp:add_object_mark' %}", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'X-CSRFToken': '{{ csrf_token }}'
|
||
},
|
||
body: `source_id=${sourceId}&mark=${mark}`
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Перезагрузить страницу для обновления таблицы
|
||
location.reload();
|
||
} else {
|
||
// Включить кнопки обратно
|
||
buttons.forEach(btn => btn.disabled = false);
|
||
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
buttons.forEach(btn => btn.disabled = false);
|
||
alert('Ошибка при добавлении наличие сигнала');
|
||
});
|
||
}
|
||
|
||
function toggleMark(markId, currentValue) {
|
||
const newValue = !currentValue;
|
||
const cell = document.querySelector(`td[data-mark-id="${markId}"]`);
|
||
const editBtn = cell.querySelector('.btn-edit-mark');
|
||
|
||
// Отключить кнопку редактирования на время запроса
|
||
if (editBtn) {
|
||
editBtn.disabled = true;
|
||
}
|
||
|
||
fetch("{% url 'mainapp:update_object_mark' %}", {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'X-CSRFToken': '{{ csrf_token }}'
|
||
},
|
||
body: `mark_id=${markId}&mark=${newValue}`
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Обновить отображение наличие сигнала без перезагрузки страницы
|
||
const statusSpan = cell.querySelector('.mark-status');
|
||
|
||
if (data.mark.mark) {
|
||
statusSpan.textContent = '✓ Есть';
|
||
statusSpan.className = 'mark-status mark-present';
|
||
} else {
|
||
statusSpan.textContent = '✗ Нет';
|
||
statusSpan.className = 'mark-status mark-absent';
|
||
}
|
||
|
||
// Обновить значение в onclick для следующего переключения
|
||
if (editBtn) {
|
||
editBtn.setAttribute('onclick', `toggleMark(${markId}, ${data.mark.mark})`);
|
||
editBtn.disabled = false;
|
||
}
|
||
|
||
// Если больше нельзя редактировать, убрать кнопку
|
||
if (!data.mark.can_edit && editBtn) {
|
||
editBtn.remove();
|
||
}
|
||
} else {
|
||
// Включить кнопку обратно при ошибке
|
||
if (editBtn) {
|
||
editBtn.disabled = false;
|
||
}
|
||
alert('Ошибка: ' + (data.error || 'Неизвестная ошибка'));
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
if (editBtn) {
|
||
editBtn.disabled = false;
|
||
}
|
||
alert('Ошибка при изменении наличие сигнала');
|
||
});
|
||
}
|
||
</script>
|
||
{% endblock %}
|