393 lines
14 KiB
HTML
393 lines
14 KiB
HTML
{% extends "base.html" %}
|
|
{% block body_class %}page-bg bg-devices{% endblock %}
|
|
{% block content %}
|
|
<style>
|
|
.device-row .form-control:disabled,
|
|
.device-row .form-select:disabled {
|
|
background-color: transparent;
|
|
border-color: transparent;
|
|
color: inherit;
|
|
opacity: 1;
|
|
padding-left: 0;
|
|
}
|
|
.device-row .form-select:disabled {
|
|
appearance: none;
|
|
}
|
|
.table td,
|
|
.table th {
|
|
white-space: normal;
|
|
word-break: break-word;
|
|
}
|
|
.row-actions {
|
|
background: #f8f9fa;
|
|
}
|
|
.row-actions__inner {
|
|
max-height: 0;
|
|
opacity: 0;
|
|
transform: translateY(-6px);
|
|
transition: max-height 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
|
|
overflow: hidden;
|
|
padding: 0 0.75rem;
|
|
}
|
|
.row-actions.show .row-actions__inner {
|
|
max-height: 60px;
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
.edit-modal {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(15, 23, 32, 0.55);
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1050;
|
|
padding: 1rem;
|
|
}
|
|
.edit-modal.show {
|
|
display: flex;
|
|
}
|
|
.edit-modal__dialog {
|
|
background: #fff;
|
|
border-radius: 0.75rem;
|
|
width: min(900px, 100%);
|
|
max-height: 85vh;
|
|
overflow: auto;
|
|
padding: 1.25rem 1.5rem;
|
|
box-shadow: 0 24px 48px rgba(15, 23, 32, 0.25);
|
|
}
|
|
.edit-modal__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.edit-modal__title {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
}
|
|
.edit-modal__footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 0.5rem;
|
|
margin-top: 1rem;
|
|
}
|
|
</style>
|
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
<h3 class="mb-0">МФУ / Принтеры</h3>
|
|
<a class="btn btn-success" href="/report/devices.xlsx">Экспорт в Excel</a>
|
|
</div>
|
|
|
|
{% if session.get('role') != 'admin' %}
|
|
<div class="alert alert-secondary">Режим просмотра: изменения доступны только администратору.</div>
|
|
{% endif %}
|
|
|
|
{% if session.get('role') == 'admin' %}
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title mb-2">Добавить устройство</h5>
|
|
<form method="post" action="/devices/add" class="row g-2">
|
|
<div class="col-md-2">
|
|
<input name="inventory_number" class="form-control" placeholder="Инв. №">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="brand" class="form-select">
|
|
<option value="">Бренд...</option>
|
|
{% for b in device_brands %}
|
|
<option value="{{ b }}">{{ b }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input name="model" class="form-control" placeholder="Модель">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input name="serial_number" class="form-control" placeholder="Серийный №">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="type" class="form-select">
|
|
<option value="">Тип...</option>
|
|
{% for value, label in device_types %}
|
|
<option value="{{ value }}">{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="cabinet_id" class="form-select">
|
|
<option value="">Кабинет...</option>
|
|
{% for cid, cname in cabinets %}
|
|
<option value="{{ cid }}">{{ cname }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<input name="note" class="form-control" placeholder="Примечание">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input name="date_in_operation" type="date" class="form-control" placeholder="Дата ввода">
|
|
</div>
|
|
<div class="col-md-2 d-grid">
|
|
<button class="btn btn-success">Добавить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<table class="table table-striped align-middle">
|
|
<tr class="device-row">
|
|
<th>Инв. №</th>
|
|
<th>Бренд</th>
|
|
<th>Модель</th>
|
|
<th>Серийный №</th>
|
|
<th>Тип</th>
|
|
<th>Кабинет</th>
|
|
<th>Примечание</th>
|
|
<th>Дата ввода</th>
|
|
<th>Добавлено</th>
|
|
</tr>
|
|
{% for id, inv, brand_value, model, sn, dtype, cab_id, cab_name, note, date_in_operation, date_added in rows %}
|
|
<tr class="js-device-row"
|
|
data-id="{{ id }}"
|
|
data-inventory-number="{{ inv }}"
|
|
data-brand="{{ brand_value or '' }}"
|
|
data-model="{{ model or '' }}"
|
|
data-serial-number="{{ sn or '' }}"
|
|
data-type="{{ dtype }}"
|
|
data-cabinet-id="{{ cab_id or '' }}"
|
|
data-date-in-operation="{{ date_in_operation.strftime('%Y-%m-%d') if date_in_operation else '' }}"
|
|
data-note="{{ note or '' }}">
|
|
{% if false %}
|
|
<form method="post" action="/devices/edit">
|
|
<input type="hidden" name="id" value="{{ id }}">
|
|
<td><input name="inventory_number" value="{{ inv }}" class="form-control form-control-sm js-edit-field" disabled></td>
|
|
<td>
|
|
<select name="brand" class="form-select form-select-sm js-edit-field" disabled>
|
|
<option value="">—</option>
|
|
{% for b in device_brands %}
|
|
<option value="{{ b }}" {% if brand_value == b %}selected{% endif %}>{{ b }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</td>
|
|
<td><input name="model" value="{{ model or '' }}" class="form-control form-control-sm js-edit-field" disabled></td>
|
|
<td><input name="serial_number" value="{{ sn or '' }}" class="form-control form-control-sm js-edit-field" disabled></td>
|
|
<td>
|
|
<select name="type" class="form-select form-select-sm js-edit-field" disabled>
|
|
{% for value, label in device_types %}
|
|
<option value="{{ value }}" {% if dtype == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<select name="cabinet_id" class="form-select form-select-sm js-edit-field" disabled>
|
|
<option value="">—</option>
|
|
{% for cid, cname in cabinets %}
|
|
<option value="{{ cid }}" {% if cab_id == cid %}selected{% endif %}>{{ cname }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</td>
|
|
<td><input name="note" value="{{ note or '' }}" class="form-control form-control-sm js-edit-field" disabled></td>
|
|
<td>
|
|
<input name="date_in_operation" type="date" value="{{ date_in_operation.strftime('%Y-%m-%d') if date_in_operation else '' }}" class="form-control form-control-sm js-edit-field" disabled>
|
|
</td>
|
|
<td>{{ date_added }}</td>
|
|
</form>
|
|
{% else %}
|
|
<td>{{ inv }}</td>
|
|
<td>{{ brand_value or '' }}</td>
|
|
<td>{{ model or '' }}</td>
|
|
<td>{{ sn or '' }}</td>
|
|
<td>{{ device_type_labels.get(dtype, dtype) }}</td>
|
|
<td>{{ cab_name or '' }}</td>
|
|
<td>{{ note or '' }}</td>
|
|
<td>{{ date_in_operation or '' }}</td>
|
|
<td>{{ date_added }}</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
{% if session.get('role') == 'admin' %}
|
|
<div class="edit-modal" id="deviceEditModal" aria-hidden="true">
|
|
<div class="edit-modal__dialog">
|
|
<div class="edit-modal__header">
|
|
<h4 class="edit-modal__title">Редактирование устройства</h4>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-modal-close>Закрыть</button>
|
|
</div>
|
|
<form method="post" action="/devices/edit" id="deviceEditForm">
|
|
<input type="hidden" name="id" id="deviceModalId">
|
|
<div class="row g-2">
|
|
<div class="col-md-3">
|
|
<input name="inventory_number" id="deviceModalInv" class="form-control" placeholder="Инв. №">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select name="brand" id="deviceModalBrand" class="form-select">
|
|
<option value="">Бренд...</option>
|
|
{% for b in device_brands %}
|
|
<option value="{{ b }}">{{ b }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input name="model" id="deviceModalModel" class="form-control" placeholder="Модель">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input name="serial_number" id="deviceModalSerial" class="form-control" placeholder="Серийный №">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select name="type" id="deviceModalType" class="form-select">
|
|
<option value="">Тип...</option>
|
|
{% for value, label in device_types %}
|
|
<option value="{{ value }}">{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select name="cabinet_id" id="deviceModalCabinet" class="form-select">
|
|
<option value="">Кабинет...</option>
|
|
{% for cid, cname in cabinets %}
|
|
<option value="{{ cid }}">{{ cname }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input name="date_in_operation" id="deviceModalDateIn" type="date" class="form-control">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<input name="note" id="deviceModalNote" class="form-control" placeholder="Примечание">
|
|
</div>
|
|
</div>
|
|
<div class="edit-modal__footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-modal-close>Отмена</button>
|
|
<button class="btn btn-primary">Сохранить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<script>
|
|
(function() {
|
|
const buttons = document.querySelectorAll('.js-edit-toggle');
|
|
if (!buttons.length) return;
|
|
buttons.forEach((btn) => {
|
|
btn.addEventListener('click', () => {
|
|
const row = btn.closest('tr');
|
|
if (!row) return;
|
|
const fields = row.querySelectorAll('.js-edit-field');
|
|
if (!fields.length) return;
|
|
fields.forEach((field) => { field.disabled = false; });
|
|
if (fields[0] && fields[0].focus) {
|
|
fields[0].focus();
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
(function() {
|
|
const rows = document.querySelectorAll('.js-device-row');
|
|
if (!rows.length) return;
|
|
const modal = document.getElementById('deviceEditModal');
|
|
const form = document.getElementById('deviceEditForm');
|
|
if (!modal || !form) return;
|
|
const modalFields = {
|
|
id: document.getElementById('deviceModalId'),
|
|
inventory_number: document.getElementById('deviceModalInv'),
|
|
brand: document.getElementById('deviceModalBrand'),
|
|
model: document.getElementById('deviceModalModel'),
|
|
serial_number: document.getElementById('deviceModalSerial'),
|
|
type: document.getElementById('deviceModalType'),
|
|
cabinet_id: document.getElementById('deviceModalCabinet'),
|
|
date_in_operation: document.getElementById('deviceModalDateIn'),
|
|
note: document.getElementById('deviceModalNote'),
|
|
};
|
|
|
|
function closeModal() {
|
|
modal.classList.remove('show');
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
}
|
|
|
|
function openModal() {
|
|
modal.classList.add('show');
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
}
|
|
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal || e.target.hasAttribute('data-modal-close')) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
function fillModalFromRow(row) {
|
|
const map = [
|
|
"id",
|
|
"inventory_number",
|
|
"brand",
|
|
"model",
|
|
"serial_number",
|
|
"type",
|
|
"cabinet_id",
|
|
"date_in_operation",
|
|
"note",
|
|
];
|
|
let filled = false;
|
|
map.forEach((name) => {
|
|
if (!modalFields[name]) return;
|
|
const dataKey = name.replace(/_/g, '-');
|
|
const attrValue = row.getAttribute(`data-${dataKey}`);
|
|
if (attrValue !== null) {
|
|
modalFields[name].value = attrValue || '';
|
|
filled = true;
|
|
}
|
|
});
|
|
if (!filled) {
|
|
const formEl = row.querySelector('form');
|
|
if (!formEl) return false;
|
|
const idInput = formEl.querySelector('input[name="id"]');
|
|
if (idInput && modalFields.id) modalFields.id.value = idInput.value;
|
|
const mapFields = map.filter((item) => item !== "id");
|
|
mapFields.forEach((name) => {
|
|
const field = formEl.querySelector(`[name="${name}"]`);
|
|
if (field && modalFields[name]) {
|
|
modalFields[name].value = field.value || '';
|
|
}
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function showActionRow(row) {
|
|
const existing = row.parentElement.querySelector('.row-actions');
|
|
if (existing) existing.remove();
|
|
const rowId = row.getAttribute('data-id') || '';
|
|
const actionRow = document.createElement('tr');
|
|
actionRow.className = 'row-actions';
|
|
actionRow.innerHTML = `<td colspan="9">
|
|
<div class="row-actions__inner">
|
|
<button type="button" class="btn btn-sm btn-outline-primary js-open-device-modal">Редактировать</button>
|
|
<form method="post" action="/devices/delete" class="d-inline ms-2">
|
|
<input type="hidden" name="id" value="${rowId}">
|
|
<button class="btn btn-sm btn-outline-danger" type="submit">Удалить</button>
|
|
</form>
|
|
</div>
|
|
</td>`;
|
|
row.insertAdjacentElement('afterend', actionRow);
|
|
const btn = actionRow.querySelector('.js-open-device-modal');
|
|
btn.addEventListener('click', () => {
|
|
if (fillModalFromRow(row)) openModal();
|
|
});
|
|
requestAnimationFrame(() => actionRow.classList.add('show'));
|
|
}
|
|
|
|
rows.forEach((row) => {
|
|
row.addEventListener('click', (e) => {
|
|
if (e.target.closest('button, input, select, textarea, a')) return;
|
|
showActionRow(row);
|
|
});
|
|
row.addEventListener('dblclick', (e) => {
|
|
if (e.target.closest('button, input, select, textarea, a')) return;
|
|
if (fillModalFromRow(row)) openModal();
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|