Доделал журнал ошибок
This commit is contained in:
@@ -29,43 +29,42 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
|
||||
</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' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'mainapp:kubsat' %}">Кубсат</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'mapsapp:3dmap' %}">3D карта</a>
|
||||
</li> -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'mapsapp:2dmap' %}">Карта</a>
|
||||
|
||||
<!-- Дропдаун "Прочее" -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarOtherDropdown" role="button" data-bs-toggle="dropdown">
|
||||
Прочее
|
||||
</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>
|
||||
|
||||
<!-- Дропдаун "Админ" (только для администраторов) -->
|
||||
{% if user.customuser.role == 'admin' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'mainapp:user_permissions_list' %}">Разрешения</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.customuser.role == 'admin' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarAdminDropdown" role="button" data-bs-toggle="dropdown">
|
||||
Админ
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{% url 'mainapp:user_permissions_list' %}">Разрешения</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Админ панель</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<!-- Пользовательское меню -->
|
||||
<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 %}
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
{% elif user.get_full_name %}
|
||||
|
||||
@@ -44,8 +44,15 @@
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
.filter-panel {
|
||||
margin-bottom: 15px;
|
||||
.btn-group .badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
font-size: 0.65rem;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
.btn-group .btn {
|
||||
position: relative;
|
||||
}
|
||||
.btn-edit {
|
||||
padding: 2px 6px;
|
||||
@@ -78,23 +85,123 @@
|
||||
<div class="container-fluid mt-3">
|
||||
<h4>Журнал ошибок и неисправностей</h4>
|
||||
|
||||
<div class="filter-panel d-flex gap-3 align-items-end flex-wrap">
|
||||
<div>
|
||||
<label class="form-label">Дата с:</label>
|
||||
<input type="date" id="dateFrom" class="form-control form-control-sm">
|
||||
<!-- 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">
|
||||
<!-- 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>
|
||||
<label class="form-label">Дата по:</label>
|
||||
<input type="date" id="dateTo" class="form-control form-control-sm">
|
||||
</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 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>
|
||||
<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 class="table-responsive">
|
||||
@@ -123,14 +230,21 @@
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editId">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Дата</label>
|
||||
<input type="date" id="editDate" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Время работы за день (ч)</label>
|
||||
<input type="number" step="0.01" min="0" id="editDailyHours" class="form-control" value="0">
|
||||
</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 class="mb-3">
|
||||
@@ -200,6 +314,21 @@ let editModal = null;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
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 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" %}?';
|
||||
if (dateFrom) url += `date_from=${dateFrom}&`;
|
||||
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 {
|
||||
const response = await fetch(url);
|
||||
const result = await response.json();
|
||||
@@ -219,12 +371,82 @@ async function loadData() {
|
||||
currentColumns = result.columns;
|
||||
|
||||
renderTable();
|
||||
updateFilterCounter();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки:', error);
|
||||
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 недели для даты
|
||||
function getWeekKey(dateStr) {
|
||||
const date = new Date(dateStr);
|
||||
@@ -295,6 +517,7 @@ function renderTable() {
|
||||
topHtml += `
|
||||
<th rowspan="2" style="width: 80px;">Раб. ч/день</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>
|
||||
`;
|
||||
@@ -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">${row.location_place_display || ''}</td>`;
|
||||
bodyHtml += `<td>${row.explanation || ''}</td>`;
|
||||
bodyHtml += `<td>${row.comment || ''}</td>`;
|
||||
bodyHtml += `</tr>`;
|
||||
@@ -384,6 +608,7 @@ function openCreateModal() {
|
||||
document.getElementById('editId').value = '';
|
||||
document.getElementById('editDate').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('editDailyHours').value = 0;
|
||||
document.getElementById('editLocationPlace').value = 'kr';
|
||||
document.getElementById('editExplanation').value = '';
|
||||
document.getElementById('editComment').value = '';
|
||||
|
||||
@@ -408,6 +633,7 @@ async function openEditModal(id) {
|
||||
document.getElementById('editId').value = row.id;
|
||||
document.getElementById('editDate').value = row.date;
|
||||
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('editComment').value = row.comment || '';
|
||||
|
||||
@@ -525,6 +751,7 @@ async function saveRecord() {
|
||||
id: document.getElementById('editId').value || null,
|
||||
date: date,
|
||||
daily_work_hours: parseFloat(document.getElementById('editDailyHours').value) || 0,
|
||||
location_place: document.getElementById('editLocationPlace').value,
|
||||
explanation: document.getElementById('editExplanation').value,
|
||||
comment: document.getElementById('editComment').value,
|
||||
downtimes: collectDowntimes(),
|
||||
|
||||
Reference in New Issue
Block a user