Пофиксил журнал ошибок

This commit is contained in:
2025-12-16 11:44:56 +03:00
parent 0b34fbd720
commit b6359d08cd
5 changed files with 258 additions and 94 deletions

View File

@@ -86,27 +86,37 @@
<h4>Журнал ошибок и неисправностей</h4>
<!-- Toolbar -->
<div class="row mb-3">
<div class="row mb-3" id="toolbar">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center gap-3">
<!-- Выбор комплекса -->
<div class="d-flex align-items-center gap-2">
<label class="form-label mb-0 text-muted">Комплекс:</label>
<select id="locationSelect" class="form-select form-select-sm" style="width: auto;">
<option value="">-- Выберите --</option>
{% for code, name in location_places %}
<option value="{{ code }}">{{ name }}</option>
{% endfor %}
</select>
</div>
<div class="vr"></div>
<!-- Action buttons -->
<div class="d-flex gap-2">
{% if user|has_perm:'errors_report_create' %}
<button class="btn btn-success btn-sm" onclick="openCreateModal()">
<button class="btn btn-success btn-sm" onclick="openCreateModal()" id="btnAddRecord" disabled>
<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">
data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters" id="btnFilters" disabled>
<i class="bi bi-funnel"></i> Фильтры
<span id="filterCounter" class="badge bg-danger" style="display: none;">0</span>
</button>
@@ -139,22 +149,6 @@
<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">
@@ -167,9 +161,6 @@
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>
@@ -185,9 +176,6 @@
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>
@@ -204,7 +192,12 @@
</div>
</div>
<div class="table-responsive">
<!-- Подсказка до выбора комплекса -->
<div class="text-center text-muted py-5" id="selectLocationHint">
<p class="mt-2">Выберите комплекс для отображения данных</p>
</div>
<div class="table-responsive" id="tableContainer" style="display: none;">
<table class="table table-bordered table-hover table-sm" id="reportTable">
<thead>
<tr id="headerRowTop">
@@ -293,27 +286,31 @@
{% block extra_js %}
<script>
const issueTypes = {
errors: [
{% for e in errors %}
{id: {{ e.id }}, name: "{{ e.name|escapejs }}"},
{% endfor %}
],
malfunctions: [
{% for m in malfunctions %}
{id: {{ m.id }}, name: "{{ m.name|escapejs }}"},
{% endfor %}
]
const locationPlaces = {
{% for code, name in location_places %}
'{{ code }}': '{{ name }}',
{% endfor %}
};
let issueTypes = {
errors: [],
malfunctions: []
};
let currentData = [];
let currentColumns = [];
let currentLocation = null;
let editModal = null;
// Инициализация
document.addEventListener('DOMContentLoaded', function() {
editModal = new bootstrap.Modal(document.getElementById('editModal'));
loadData();
// Обработчик выбора комплекса
const locationSelect = document.getElementById('locationSelect');
locationSelect.addEventListener('change', function() {
onLocationChange(this.value);
});
// Добавляем обработчики для обновления счетчика фильтров
const form = document.getElementById('filter-form');
@@ -331,15 +328,63 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// Обработка смены комплекса
function onLocationChange(location) {
const hint = document.getElementById('selectLocationHint');
const tableContainer = document.getElementById('tableContainer');
const btnAdd = document.getElementById('btnAddRecord');
const btnFilters = document.getElementById('btnFilters');
if (!location) {
// Сбрасываем состояние
currentLocation = null;
currentData = [];
currentColumns = [];
issueTypes = { errors: [], malfunctions: [] };
// Показываем hint, скрываем таблицу
hint.style.display = 'block';
tableContainer.style.display = 'none';
// Очищаем таблицу
document.getElementById('reportBody').innerHTML = '';
document.getElementById('headerRowTop').innerHTML = '';
document.getElementById('headerRowBottom').innerHTML = '';
// Отключаем кнопки
if (btnAdd) btnAdd.disabled = true;
if (btnFilters) btnFilters.disabled = true;
return;
}
currentLocation = location;
// Скрываем hint, показываем таблицу
hint.style.display = 'none';
tableContainer.style.display = 'block';
// Включаем кнопки
if (btnAdd) btnAdd.disabled = false;
if (btnFilters) btnFilters.disabled = false;
// Сбрасываем фильтры при смене комплекса
document.getElementById('dateFrom').value = '';
document.getElementById('dateTo').value = '';
selectAllOptions('errorFilter', false);
selectAllOptions('malfunctionFilter', false);
// Загружаем данные
loadData();
}
// Загрузка данных
async function loadData() {
if (!currentLocation) return;
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);
@@ -347,14 +392,10 @@ async function loadData() {
const malfunctionFilters = Array.from(malfunctionSelect.selectedOptions).map(opt => opt.value);
let url = '{% url "mainapp:errors_report_api" %}?';
url += `location_place=${currentLocation}&`;
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}&`;
});
@@ -370,6 +411,13 @@ async function loadData() {
currentData = result.data;
currentColumns = result.columns;
// Обновляем issueTypes для модального окна
issueTypes.errors = currentColumns.filter(c => c.category === 'error');
issueTypes.malfunctions = currentColumns.filter(c => c.category === 'malfunction');
// Обновляем фильтры
updateFilterOptions();
renderTable();
updateFilterCounter();
} catch (error) {
@@ -378,6 +426,36 @@ async function loadData() {
}
}
// Обновление опций фильтров на основе текущего комплекса
function updateFilterOptions() {
const errorSelect = document.getElementById('errorFilter');
const malfunctionSelect = document.getElementById('malfunctionFilter');
// Сохраняем текущий выбор
const selectedErrors = Array.from(errorSelect.selectedOptions).map(opt => opt.value);
const selectedMalfunctions = Array.from(malfunctionSelect.selectedOptions).map(opt => opt.value);
// Обновляем ошибки
errorSelect.innerHTML = '';
issueTypes.errors.forEach(e => {
const opt = document.createElement('option');
opt.value = e.id;
opt.textContent = e.name;
if (selectedErrors.includes(String(e.id))) opt.selected = true;
errorSelect.appendChild(opt);
});
// Обновляем неисправности
malfunctionSelect.innerHTML = '';
issueTypes.malfunctions.forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name;
if (selectedMalfunctions.includes(String(m.id))) opt.selected = true;
malfunctionSelect.appendChild(opt);
});
}
// Функция для выбора/снятия всех опций в select
function selectAllOptions(selectId, selectAll) {
const selectElement = document.getElementById(selectId);
@@ -404,11 +482,12 @@ function resetFilters() {
document.getElementById('dateTo').value = '';
// Снимаем выбор со всех мультиселектов
selectAllOptions('locationFilter', false);
selectAllOptions('errorFilter', false);
selectAllOptions('malfunctionFilter', false);
loadData();
if (currentLocation) {
loadData();
}
// Закрыть offcanvas
const offcanvas = bootstrap.Offcanvas.getInstance(document.getElementById('offcanvasFilters'));
@@ -425,10 +504,6 @@ function updateFilterCounter() {
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++;
@@ -517,7 +592,6 @@ 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>
`;
@@ -583,7 +657,6 @@ 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>`;
@@ -604,11 +677,17 @@ function renderMarkCell(value) {
// Открыть модальное окно для создания
function openCreateModal() {
if (!currentLocation) {
alert('Сначала выберите комплекс');
return;
}
document.getElementById('modalTitle').textContent = 'Новая запись';
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('editLocationPlace').value = currentLocation;
document.getElementById('editLocationPlace').disabled = true; // Комплекс фиксирован
document.getElementById('editExplanation').value = '';
document.getElementById('editComment').value = '';
@@ -633,7 +712,8 @@ 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('editLocationPlace').value = row.location_place || currentLocation;
document.getElementById('editLocationPlace').disabled = true; // Комплекс фиксирован
document.getElementById('editExplanation').value = row.explanation || '';
document.getElementById('editComment').value = row.comment || '';
@@ -694,27 +774,35 @@ function removeDowntimeRow(btn) {
// Рендер чекбоксов ошибок/неисправностей
function renderIssueCheckboxes(row) {
let errorsHtml = '';
issueTypes.errors.forEach(e => {
const checked = row[`issue_${e.id}`] ? 'checked' : '';
errorsHtml += `
<div class="form-check">
<input class="form-check-input issue-check" type="checkbox" id="issue_${e.id}" data-issue-id="${e.id}" ${checked}>
<label class="form-check-label" for="issue_${e.id}">${e.name}</label>
</div>
`;
});
if (issueTypes.errors.length === 0) {
errorsHtml = '<p class="text-muted small">Нет ошибок для этого комплекса</p>';
} else {
issueTypes.errors.forEach(e => {
const checked = row[`issue_${e.id}`] ? 'checked' : '';
errorsHtml += `
<div class="form-check">
<input class="form-check-input issue-check" type="checkbox" id="issue_${e.id}" data-issue-id="${e.id}" ${checked}>
<label class="form-check-label" for="issue_${e.id}">${e.name}</label>
</div>
`;
});
}
document.getElementById('editErrors').innerHTML = errorsHtml;
let malfHtml = '';
issueTypes.malfunctions.forEach(m => {
const checked = row[`issue_${m.id}`] ? 'checked' : '';
malfHtml += `
<div class="form-check">
<input class="form-check-input issue-check" type="checkbox" id="issue_${m.id}" data-issue-id="${m.id}" ${checked}>
<label class="form-check-label" for="issue_${m.id}">${m.name}</label>
</div>
`;
});
if (issueTypes.malfunctions.length === 0) {
malfHtml = '<p class="text-muted small">Нет неисправностей для этого комплекса</p>';
} else {
issueTypes.malfunctions.forEach(m => {
const checked = row[`issue_${m.id}`] ? 'checked' : '';
malfHtml += `
<div class="form-check">
<input class="form-check-input issue-check" type="checkbox" id="issue_${m.id}" data-issue-id="${m.id}" ${checked}>
<label class="form-check-label" for="issue_${m.id}">${m.name}</label>
</div>
`;
});
}
document.getElementById('editMalfunctions').innerHTML = malfHtml;
}
@@ -751,7 +839,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,
location_place: currentLocation, // Используем текущий выбранный комплекс
explanation: document.getElementById('editExplanation').value,
comment: document.getElementById('editComment').value,
downtimes: collectDowntimes(),