Files
Inv_web/source/templates/devices.html
2026-02-24 15:48:14 -08:00

509 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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;
}
.cartridge-cell-editable {
cursor: pointer;
}
.cartridge-editor-row {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.cartridge-editor-row:last-child {
margin-bottom: 0;
}
</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">
<input name="cartridge" class="form-control" placeholder="Картридж">
</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>
<th>Добавлено</th>
</tr>
{% for id, inv, brand_value, model, sn, dtype, cartridge, 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-cartridge="{{ cartridge or '' }}"
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><input name="cartridge" value="{{ cartridge or '' }}" class="form-control form-control-sm js-edit-field" disabled></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
class="{% if session.get('role') == 'admin' %}js-cartridge-cell cartridge-cell-editable{% endif %}"
data-device-id="{{ id }}"
data-cartridge="{{ cartridge or '' }}"
>{{ cartridge or '' }}</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">
<input name="cartridge" id="deviceModalCartridge" class="form-control" placeholder="Картридж">
</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>
<div class="edit-modal" id="deviceCartridgeModal" 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-cartridge-close>Закрыть</button>
</div>
<form method="post" action="/devices/update_cartridge" id="deviceCartridgeForm">
<input type="hidden" name="id" id="deviceCartridgeId">
<div id="deviceCartridgeList"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" id="deviceCartridgeAddBtn">Добавить картридж</button>
<div class="edit-modal__footer">
<button type="button" class="btn btn-outline-secondary" data-cartridge-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'),
cartridge: document.getElementById('deviceModalCartridge'),
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",
"cartridge",
"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="10">
<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();
});
});
})();
(function() {
const modal = document.getElementById('deviceCartridgeModal');
const form = document.getElementById('deviceCartridgeForm');
const list = document.getElementById('deviceCartridgeList');
const addBtn = document.getElementById('deviceCartridgeAddBtn');
const idInput = document.getElementById('deviceCartridgeId');
const cells = document.querySelectorAll('.js-cartridge-cell');
if (!modal || !form || !list || !addBtn || !idInput || !cells.length) return;
function splitCartridges(raw) {
return (raw || '')
.split(/[\n,;]+/)
.map((item) => item.trim())
.filter(Boolean);
}
function closeModal() {
modal.classList.remove('show');
modal.setAttribute('aria-hidden', 'true');
}
function openModal() {
modal.classList.add('show');
modal.setAttribute('aria-hidden', 'false');
}
function addInput(value) {
const row = document.createElement('div');
row.className = 'cartridge-editor-row';
const input = document.createElement('input');
input.name = 'cartridge_models';
input.className = 'form-control';
input.placeholder = 'Модель картриджа';
input.value = value || '';
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'btn btn-outline-danger btn-sm';
removeBtn.setAttribute('aria-label', 'Удалить');
removeBtn.textContent = '×';
removeBtn.addEventListener('click', () => row.remove());
row.appendChild(input);
row.appendChild(removeBtn);
list.appendChild(row);
}
function fillFromCell(cell) {
const deviceId = cell.getAttribute('data-device-id') || '';
idInput.value = deviceId;
list.innerHTML = '';
const parsed = splitCartridges(cell.getAttribute('data-cartridge') || '');
if (parsed.length) {
parsed.forEach((item) => addInput(item));
} else {
addInput('');
}
}
addBtn.addEventListener('click', () => addInput(''));
modal.addEventListener('click', (e) => {
if (e.target === modal || e.target.hasAttribute('data-cartridge-close')) {
closeModal();
}
});
cells.forEach((cell) => {
cell.addEventListener('click', (e) => {
e.stopPropagation();
fillFromCell(cell);
openModal();
});
});
})();
</script>
{% endblock %}