Доделал журнал ошибок
This commit is contained in:
18
dbapp/mainapp/migrations/0029_dailyreport_location_place.py
Normal file
18
dbapp/mainapp/migrations/0029_dailyreport_location_place.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-16 06:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mainapp', '0028_remove_issue_type_fields'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dailyreport',
|
||||||
|
name='location_place',
|
||||||
|
field=models.CharField(choices=[('kr', 'КР'), ('dv', 'ДВ')], default='kr', help_text='К какому комплексу принадлежит журнал', max_length=30, null=True, verbose_name='Комплекс'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -30,6 +30,10 @@ class IssueType(models.Model):
|
|||||||
|
|
||||||
class DailyReport(models.Model):
|
class DailyReport(models.Model):
|
||||||
"""Ежедневный отчёт"""
|
"""Ежедневный отчёт"""
|
||||||
|
PLACES = [
|
||||||
|
("kr", "КР"),
|
||||||
|
("dv", "ДВ")
|
||||||
|
]
|
||||||
date = models.DateField(
|
date = models.DateField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name="Дата",
|
verbose_name="Дата",
|
||||||
@@ -50,6 +54,14 @@ class DailyReport(models.Model):
|
|||||||
)
|
)
|
||||||
explanation = models.TextField(blank=True, null=True, verbose_name='Пояснение')
|
explanation = models.TextField(blank=True, null=True, verbose_name='Пояснение')
|
||||||
comment = models.TextField(blank=True, null=True, verbose_name='Комментарий')
|
comment = models.TextField(blank=True, null=True, verbose_name='Комментарий')
|
||||||
|
location_place = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=PLACES,
|
||||||
|
null=True,
|
||||||
|
default="kr",
|
||||||
|
verbose_name="Комплекс",
|
||||||
|
help_text="К какому комплексу принадлежит журнал",
|
||||||
|
)
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создано")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создано")
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Обновлено")
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="Обновлено")
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class Band(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return f"{self.name}({int(self.border_start)}-{int(self.border_end)})МГц"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Диапазон"
|
verbose_name = "Диапазон"
|
||||||
|
|||||||
@@ -29,43 +29,42 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
|
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'lyngsatapp:lyngsat_list' %}">Справочные данные</a>
|
|
||||||
</li>
|
|
||||||
<!-- <li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
|
|
||||||
</li> -->
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'mainapp:signal_marks' %}">Отметки сигналов</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'mainapp:errors_report' %}">Журнал ошибок</a>
|
|
||||||
</li>
|
|
||||||
{% if user|has_perm:'kubsat_view' %}
|
{% if user|has_perm:'kubsat_view' %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
|
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- <li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
|
<!-- Дропдаун "Прочее" -->
|
||||||
</li> -->
|
<li class="nav-item dropdown">
|
||||||
<li class="nav-item">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarOtherDropdown" role="button" data-bs-toggle="dropdown">
|
||||||
<a class="nav-link" href="{% url 'mapsapp:2dmap' %}">Карта</a>
|
Прочее
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'lyngsatapp:lyngsat_list' %}">Справочные данные</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'mainapp:signal_marks' %}">Отметки сигналов</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'mainapp:errors_report' %}">Журнал ошибок</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'mapsapp:2dmap' %}">Карта</a></li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Дропдаун "Админ" (только для администраторов) -->
|
||||||
{% if user.customuser.role == 'admin' %}
|
{% if user.customuser.role == 'admin' %}
|
||||||
<li class="nav-item">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link" href="{% url 'mainapp:user_permissions_list' %}">Разрешения</a>
|
<a class="nav-link dropdown-toggle" href="#" id="navbarAdminDropdown" role="button" data-bs-toggle="dropdown">
|
||||||
</li>
|
Админ
|
||||||
{% endif %}
|
</a>
|
||||||
{% if user.customuser.role == 'admin' %}
|
<ul class="dropdown-menu">
|
||||||
<li class="nav-item">
|
<li><a class="dropdown-item" href="{% url 'mainapp:user_permissions_list' %}">Разрешения</a></li>
|
||||||
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
|
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Админ панель</a></li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
|
<!-- Пользовательское меню -->
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarUserDropdown" role="button" data-bs-toggle="dropdown">
|
||||||
{% if user.first_name and user.last_name %}
|
{% if user.first_name and user.last_name %}
|
||||||
{{ user.first_name }} {{ user.last_name }}
|
{{ user.first_name }} {{ user.last_name }}
|
||||||
{% elif user.get_full_name %}
|
{% elif user.get_full_name %}
|
||||||
|
|||||||
@@ -44,8 +44,15 @@
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
.filter-panel {
|
.btn-group .badge {
|
||||||
margin-bottom: 15px;
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
.btn-group .btn {
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.btn-edit {
|
.btn-edit {
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
@@ -78,23 +85,123 @@
|
|||||||
<div class="container-fluid mt-3">
|
<div class="container-fluid mt-3">
|
||||||
<h4>Журнал ошибок и неисправностей</h4>
|
<h4>Журнал ошибок и неисправностей</h4>
|
||||||
|
|
||||||
<div class="filter-panel d-flex gap-3 align-items-end flex-wrap">
|
<!-- Toolbar -->
|
||||||
<div>
|
<div class="row mb-3">
|
||||||
<label class="form-label">Дата с:</label>
|
<div class="col-12">
|
||||||
<input type="date" id="dateFrom" class="form-control form-control-sm">
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
{% if user|has_perm:'errors_report_create' %}
|
||||||
|
<button class="btn btn-success btn-sm" onclick="openCreateModal()">
|
||||||
|
<i class="bi bi-plus-lg"></i> Добавить запись
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- <button class="btn btn-primary btn-sm" onclick="loadData()">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i> Обновить
|
||||||
|
</button> -->
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<label class="form-label">Дата по:</label>
|
|
||||||
<input type="date" id="dateTo" class="form-control form-control-sm">
|
<!-- 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 id="filter-form">
|
||||||
|
<!-- Date Filter -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">
|
||||||
|
Период
|
||||||
|
</label>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small">Дата с:</label>
|
||||||
|
<input type="date" id="dateFrom" name="date_from" class="form-control form-control-sm">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small">Дата по:</label>
|
||||||
|
<input type="date" id="dateTo" name="date_to" class="form-control form-control-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Location Filter -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<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('locationFilter', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('locationFilter', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select id="locationFilter" name="location_place" class="form-select form-select-sm" multiple size="2">
|
||||||
|
<option value="kr">КР</option>
|
||||||
|
<option value="dv">ДВ</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<!-- Error Filter -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<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('errorFilter', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('errorFilter', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select id="errorFilter" name="error_filter" class="form-select form-select-sm" multiple size="6">
|
||||||
|
{% for e in errors %}
|
||||||
|
<option value="{{ e.id }}">{{ e.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Malfunction Filter -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<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('malfunctionFilter', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('malfunctionFilter', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select id="malfunctionFilter" name="malfunction_filter" class="form-select form-select-sm" multiple size="6">
|
||||||
|
{% for m in malfunctions %}
|
||||||
|
<option value="{{ m.id }}">{{ m.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Apply Filters and Reset Buttons -->
|
||||||
|
<div class="d-grid gap-2 mt-4">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" onclick="applyFilters()">
|
||||||
|
Применить
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" onclick="resetFilters()">
|
||||||
|
Сбросить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary btn-sm" onclick="loadData()">
|
|
||||||
Применить
|
|
||||||
</button>
|
|
||||||
{% if user|has_perm:'errors_report_create' %}
|
|
||||||
<button class="btn btn-success btn-sm" onclick="openCreateModal()">
|
|
||||||
<i class="bi bi-plus-circle"></i> Создать
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -123,14 +230,21 @@
|
|||||||
<form id="editForm">
|
<form id="editForm">
|
||||||
<input type="hidden" id="editId">
|
<input type="hidden" id="editId">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<label class="form-label">Дата</label>
|
<label class="form-label">Дата</label>
|
||||||
<input type="date" id="editDate" class="form-control" required>
|
<input type="date" id="editDate" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<label class="form-label">Время работы за день (ч)</label>
|
<label class="form-label">Время работы за день (ч)</label>
|
||||||
<input type="number" step="0.01" min="0" id="editDailyHours" class="form-control" value="0">
|
<input type="number" step="0.01" min="0" id="editDailyHours" class="form-control" value="0">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Комплекс</label>
|
||||||
|
<select id="editLocationPlace" class="form-select" required>
|
||||||
|
<option value="kr">КР</option>
|
||||||
|
<option value="dv">ДВ</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -200,6 +314,21 @@ let editModal = null;
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||||
loadData();
|
loadData();
|
||||||
|
|
||||||
|
// Добавляем обработчики для обновления счетчика фильтров
|
||||||
|
const form = document.getElementById('filter-form');
|
||||||
|
if (form) {
|
||||||
|
const inputFields = form.querySelectorAll('input[type="date"], select');
|
||||||
|
inputFields.forEach(input => {
|
||||||
|
input.addEventListener('change', updateFilterCounter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем счетчик при открытии offcanvas
|
||||||
|
const offcanvasElement = document.getElementById('offcanvasFilters');
|
||||||
|
if (offcanvasElement) {
|
||||||
|
offcanvasElement.addEventListener('show.bs.offcanvas', updateFilterCounter);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Загрузка данных
|
// Загрузка данных
|
||||||
@@ -207,10 +336,33 @@ async function loadData() {
|
|||||||
const dateFrom = document.getElementById('dateFrom').value;
|
const dateFrom = document.getElementById('dateFrom').value;
|
||||||
const dateTo = document.getElementById('dateTo').value;
|
const dateTo = document.getElementById('dateTo').value;
|
||||||
|
|
||||||
|
// Получаем выбранные значения из мультиселектов
|
||||||
|
const locationSelect = document.getElementById('locationFilter');
|
||||||
|
const locationPlaces = Array.from(locationSelect.selectedOptions).map(opt => opt.value);
|
||||||
|
|
||||||
|
const errorSelect = document.getElementById('errorFilter');
|
||||||
|
const errorFilters = Array.from(errorSelect.selectedOptions).map(opt => opt.value);
|
||||||
|
|
||||||
|
const malfunctionSelect = document.getElementById('malfunctionFilter');
|
||||||
|
const malfunctionFilters = Array.from(malfunctionSelect.selectedOptions).map(opt => opt.value);
|
||||||
|
|
||||||
let url = '{% url "mainapp:errors_report_api" %}?';
|
let url = '{% url "mainapp:errors_report_api" %}?';
|
||||||
if (dateFrom) url += `date_from=${dateFrom}&`;
|
if (dateFrom) url += `date_from=${dateFrom}&`;
|
||||||
if (dateTo) url += `date_to=${dateTo}&`;
|
if (dateTo) url += `date_to=${dateTo}&`;
|
||||||
|
|
||||||
|
// Добавляем множественные значения
|
||||||
|
locationPlaces.forEach(loc => {
|
||||||
|
url += `location_place=${loc}&`;
|
||||||
|
});
|
||||||
|
|
||||||
|
errorFilters.forEach(err => {
|
||||||
|
url += `error_filter=${err}&`;
|
||||||
|
});
|
||||||
|
|
||||||
|
malfunctionFilters.forEach(mal => {
|
||||||
|
url += `malfunction_filter=${mal}&`;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@@ -219,12 +371,82 @@ async function loadData() {
|
|||||||
currentColumns = result.columns;
|
currentColumns = result.columns;
|
||||||
|
|
||||||
renderTable();
|
renderTable();
|
||||||
|
updateFilterCounter();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки:', error);
|
console.error('Ошибка загрузки:', error);
|
||||||
alert('Ошибка загрузки данных');
|
alert('Ошибка загрузки данных');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для выбора/снятия всех опций в select
|
||||||
|
function selectAllOptions(selectId, selectAll) {
|
||||||
|
const selectElement = document.getElementById(selectId);
|
||||||
|
if (selectElement) {
|
||||||
|
for (let i = 0; i < selectElement.options.length; i++) {
|
||||||
|
selectElement.options[i].selected = selectAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применить фильтры
|
||||||
|
function applyFilters() {
|
||||||
|
loadData();
|
||||||
|
// Закрыть offcanvas
|
||||||
|
const offcanvas = bootstrap.Offcanvas.getInstance(document.getElementById('offcanvasFilters'));
|
||||||
|
if (offcanvas) {
|
||||||
|
offcanvas.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сбросить фильтры
|
||||||
|
function resetFilters() {
|
||||||
|
document.getElementById('dateFrom').value = '';
|
||||||
|
document.getElementById('dateTo').value = '';
|
||||||
|
|
||||||
|
// Снимаем выбор со всех мультиселектов
|
||||||
|
selectAllOptions('locationFilter', false);
|
||||||
|
selectAllOptions('errorFilter', false);
|
||||||
|
selectAllOptions('malfunctionFilter', false);
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
|
||||||
|
// Закрыть offcanvas
|
||||||
|
const offcanvas = bootstrap.Offcanvas.getInstance(document.getElementById('offcanvasFilters'));
|
||||||
|
if (offcanvas) {
|
||||||
|
offcanvas.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить счетчик фильтров
|
||||||
|
function updateFilterCounter() {
|
||||||
|
let filterCount = 0;
|
||||||
|
|
||||||
|
// Проверяем даты
|
||||||
|
if (document.getElementById('dateFrom').value) filterCount++;
|
||||||
|
if (document.getElementById('dateTo').value) filterCount++;
|
||||||
|
|
||||||
|
// Проверяем мультиселекты
|
||||||
|
const locationSelect = document.getElementById('locationFilter');
|
||||||
|
if (locationSelect.selectedOptions.length > 0) filterCount++;
|
||||||
|
|
||||||
|
const errorSelect = document.getElementById('errorFilter');
|
||||||
|
if (errorSelect.selectedOptions.length > 0) filterCount++;
|
||||||
|
|
||||||
|
const malfunctionSelect = document.getElementById('malfunctionFilter');
|
||||||
|
if (malfunctionSelect.selectedOptions.length > 0) filterCount++;
|
||||||
|
|
||||||
|
// Отображаем счетчик
|
||||||
|
const counterElement = document.getElementById('filterCounter');
|
||||||
|
if (counterElement) {
|
||||||
|
if (filterCount > 0) {
|
||||||
|
counterElement.textContent = filterCount;
|
||||||
|
counterElement.style.display = 'inline';
|
||||||
|
} else {
|
||||||
|
counterElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Получить номер ISO недели для даты
|
// Получить номер ISO недели для даты
|
||||||
function getWeekKey(dateStr) {
|
function getWeekKey(dateStr) {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
@@ -295,6 +517,7 @@ function renderTable() {
|
|||||||
topHtml += `
|
topHtml += `
|
||||||
<th rowspan="2" style="width: 80px;">Раб. ч/день</th>
|
<th rowspan="2" style="width: 80px;">Раб. ч/день</th>
|
||||||
<th rowspan="2" style="width: 100px;">Раб. ч/нед.</th>
|
<th rowspan="2" style="width: 100px;">Раб. ч/нед.</th>
|
||||||
|
<th rowspan="2" style="width: 80px;">Комплекс</th>
|
||||||
<th rowspan="2" style="min-width: 150px;">Пояснение</th>
|
<th rowspan="2" style="min-width: 150px;">Пояснение</th>
|
||||||
<th rowspan="2" style="min-width: 150px;">Комментарий</th>
|
<th rowspan="2" style="min-width: 150px;">Комментарий</th>
|
||||||
`;
|
`;
|
||||||
@@ -360,6 +583,7 @@ function renderTable() {
|
|||||||
bodyHtml += `<td class="text-center align-middle weekly-hours-cell" rowspan="${info.rowspan}">${info.weeklyTotal}</td>`;
|
bodyHtml += `<td class="text-center align-middle weekly-hours-cell" rowspan="${info.rowspan}">${info.weeklyTotal}</td>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bodyHtml += `<td class="text-center">${row.location_place_display || ''}</td>`;
|
||||||
bodyHtml += `<td>${row.explanation || ''}</td>`;
|
bodyHtml += `<td>${row.explanation || ''}</td>`;
|
||||||
bodyHtml += `<td>${row.comment || ''}</td>`;
|
bodyHtml += `<td>${row.comment || ''}</td>`;
|
||||||
bodyHtml += `</tr>`;
|
bodyHtml += `</tr>`;
|
||||||
@@ -384,6 +608,7 @@ function openCreateModal() {
|
|||||||
document.getElementById('editId').value = '';
|
document.getElementById('editId').value = '';
|
||||||
document.getElementById('editDate').value = new Date().toISOString().split('T')[0];
|
document.getElementById('editDate').value = new Date().toISOString().split('T')[0];
|
||||||
document.getElementById('editDailyHours').value = 0;
|
document.getElementById('editDailyHours').value = 0;
|
||||||
|
document.getElementById('editLocationPlace').value = 'kr';
|
||||||
document.getElementById('editExplanation').value = '';
|
document.getElementById('editExplanation').value = '';
|
||||||
document.getElementById('editComment').value = '';
|
document.getElementById('editComment').value = '';
|
||||||
|
|
||||||
@@ -408,6 +633,7 @@ async function openEditModal(id) {
|
|||||||
document.getElementById('editId').value = row.id;
|
document.getElementById('editId').value = row.id;
|
||||||
document.getElementById('editDate').value = row.date;
|
document.getElementById('editDate').value = row.date;
|
||||||
document.getElementById('editDailyHours').value = row.daily_work_hours || 0;
|
document.getElementById('editDailyHours').value = row.daily_work_hours || 0;
|
||||||
|
document.getElementById('editLocationPlace').value = row.location_place || 'kr';
|
||||||
document.getElementById('editExplanation').value = row.explanation || '';
|
document.getElementById('editExplanation').value = row.explanation || '';
|
||||||
document.getElementById('editComment').value = row.comment || '';
|
document.getElementById('editComment').value = row.comment || '';
|
||||||
|
|
||||||
@@ -525,6 +751,7 @@ async function saveRecord() {
|
|||||||
id: document.getElementById('editId').value || null,
|
id: document.getElementById('editId').value || null,
|
||||||
date: date,
|
date: date,
|
||||||
daily_work_hours: parseFloat(document.getElementById('editDailyHours').value) || 0,
|
daily_work_hours: parseFloat(document.getElementById('editDailyHours').value) || 0,
|
||||||
|
location_place: document.getElementById('editLocationPlace').value,
|
||||||
explanation: document.getElementById('editExplanation').value,
|
explanation: document.getElementById('editExplanation').value,
|
||||||
comment: document.getElementById('editComment').value,
|
comment: document.getElementById('editComment').value,
|
||||||
downtimes: collectDowntimes(),
|
downtimes: collectDowntimes(),
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
|||||||
# Получаем параметры фильтрации
|
# Получаем параметры фильтрации
|
||||||
date_from = request.GET.get('date_from')
|
date_from = request.GET.get('date_from')
|
||||||
date_to = request.GET.get('date_to')
|
date_to = request.GET.get('date_to')
|
||||||
|
location_places = request.GET.getlist('location_place')
|
||||||
|
error_filters = request.GET.getlist('error_filter')
|
||||||
|
malfunction_filters = request.GET.getlist('malfunction_filter')
|
||||||
|
|
||||||
reports = DailyReport.objects.all()
|
reports = DailyReport.objects.all()
|
||||||
|
|
||||||
@@ -38,8 +41,16 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
|||||||
reports = reports.filter(date__gte=date_from)
|
reports = reports.filter(date__gte=date_from)
|
||||||
if date_to:
|
if date_to:
|
||||||
reports = reports.filter(date__lte=date_to)
|
reports = reports.filter(date__lte=date_to)
|
||||||
|
if location_places:
|
||||||
|
reports = reports.filter(location_place__in=location_places)
|
||||||
|
|
||||||
reports = reports.prefetch_related('downtime_periods', 'issue_marks__issue_type')
|
# Фильтрация по ошибкам/неисправностям
|
||||||
|
if error_filters:
|
||||||
|
reports = reports.filter(issue_marks__issue_type_id__in=error_filters, issue_marks__is_present=True)
|
||||||
|
if malfunction_filters:
|
||||||
|
reports = reports.filter(issue_marks__issue_type_id__in=malfunction_filters, issue_marks__is_present=True)
|
||||||
|
|
||||||
|
reports = reports.prefetch_related('downtime_periods', 'issue_marks__issue_type').distinct()
|
||||||
|
|
||||||
# Получаем все типы ошибок/неисправностей
|
# Получаем все типы ошибок/неисправностей
|
||||||
issue_types = IssueType.objects.all().order_by('category', 'name')
|
issue_types = IssueType.objects.all().order_by('category', 'name')
|
||||||
@@ -68,6 +79,8 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
|||||||
'weekly_work_hours': float(report.weekly_work_hours),
|
'weekly_work_hours': float(report.weekly_work_hours),
|
||||||
'explanation': report.explanation or '',
|
'explanation': report.explanation or '',
|
||||||
'comment': report.comment or '',
|
'comment': report.comment or '',
|
||||||
|
'location_place': report.location_place or '',
|
||||||
|
'location_place_display': report.get_location_place_display() if report.location_place else '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Добавляем отметки по каждому типу
|
# Добавляем отметки по каждому типу
|
||||||
@@ -129,6 +142,7 @@ class ErrorsReportSaveAPIView(View):
|
|||||||
report.daily_work_hours = Decimal(str(data.get('daily_work_hours', 0)))
|
report.daily_work_hours = Decimal(str(data.get('daily_work_hours', 0)))
|
||||||
report.explanation = data.get('explanation', '')
|
report.explanation = data.get('explanation', '')
|
||||||
report.comment = data.get('comment', '')
|
report.comment = data.get('comment', '')
|
||||||
|
report.location_place = data.get('location_place', 'kr')
|
||||||
report.save()
|
report.save()
|
||||||
except DailyReport.DoesNotExist:
|
except DailyReport.DoesNotExist:
|
||||||
return JsonResponse({'success': False, 'error': 'Запись не найдена'}, status=404)
|
return JsonResponse({'success': False, 'error': 'Запись не найдена'}, status=404)
|
||||||
@@ -139,6 +153,7 @@ class ErrorsReportSaveAPIView(View):
|
|||||||
daily_work_hours=Decimal(str(data.get('daily_work_hours', 0))),
|
daily_work_hours=Decimal(str(data.get('daily_work_hours', 0))),
|
||||||
explanation=data.get('explanation', ''),
|
explanation=data.get('explanation', ''),
|
||||||
comment=data.get('comment', ''),
|
comment=data.get('comment', ''),
|
||||||
|
location_place=data.get('location_place', 'kr'),
|
||||||
created_by=request.user if request.user.is_authenticated else None,
|
created_by=request.user if request.user.is_authenticated else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class SatelliteListView(LoginRequiredMixin, View):
|
|||||||
processed_satellites = []
|
processed_satellites = []
|
||||||
for satellite in page_obj:
|
for satellite in page_obj:
|
||||||
# Get band names
|
# Get band names
|
||||||
band_names = [band.name for band in satellite.band.all()]
|
band_names = [f"{band.name}({int(band.border_start)}-{int(band.border_end)})" for band in satellite.band.all()]
|
||||||
|
|
||||||
# Get location_place display value
|
# Get location_place display value
|
||||||
location_place_display = dict(Satellite.PLACES).get(satellite.location_place, "-") if satellite.location_place else "-"
|
location_place_display = dict(Satellite.PLACES).get(satellite.location_place, "-") if satellite.location_place else "-"
|
||||||
|
|||||||
Reference in New Issue
Block a user