Files
Inv_web/source/templates/consumables.html
2026-02-23 20:59:05 +03:00

316 lines
11 KiB
HTML

{% extends "base.html" %}
{% block body_class %}page-bg bg-cartridges{% endblock %}
{% block content %}
<style>
.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(800px, 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/consumables.xlsx">Экспорт в Excel</a>
</div>
{% if session.get('role') != 'admin' %}
<div class="alert alert-secondary">Режим просмотра: изменения доступны только администратору.</div>
{% endif %}
{% if session.get('role') == 'admin' %}
<form class="row g-2 mb-3" method="post" action="/consumables/add">
<div class="col-md-3">
<input class="form-control" placeholder="Штрихкод" name="barcode">
</div>
<div class="col-md-3">
<input class="form-control" placeholder="Модель" name="model">
</div>
<div class="col-md-3">
<div class="d-flex gap-2">
<select class="form-select" data-type-select>
<option value="">Тип...</option>
<option value="Фотобарабан">Фотобарабан</option>
<option value="Ролики захвата бумаги">Ролики захвата бумаги</option>
<option value="Ролики захвата ADF">Ролики захвата ADF</option>
<option value="Термоузел(Печка)">Термоузел(Печка)</option>
<option value="__custom__">Другое...</option>
</select>
<input class="form-control" placeholder="Свой тип" data-type-custom>
<input type="hidden" name="type" data-type-value>
</div>
</div>
<div class="col-md-2">
<input class="form-control" placeholder="Количество" name="quantity">
</div>
<div class="col-md-1 d-grid">
<button class="btn btn-success">Добавить</button>
</div>
</form>
{% endif %}
<table class="table table-striped">
<tr>
<th>Штрихкод</th>
<th>Модель</th>
<th>Тип</th>
<th>Количество</th>
</tr>
{% for barcode, model, ctype, qty in rows %}
<tr class="js-consumable-row"
data-barcode="{{ barcode }}"
data-model="{{ model or '' }}"
data-type="{{ ctype or '' }}"
data-quantity="{{ qty }}">
<td>{{ barcode }}</td>
<td>{{ model }}</td>
<td>{{ ctype }}</td>
<td>{{ qty }}</td>
</tr>
{% endfor %}
</table>
{% if session.get('role') == 'admin' %}
<div class="edit-modal" id="consumableEditModal" 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="/consumables/edit" id="consumableEditForm">
<div class="row g-2">
<div class="col-md-3">
<input name="barcode" id="consModalBarcode" class="form-control" placeholder="Штрихкод" readonly>
</div>
<div class="col-md-3">
<input name="model" id="consModalModel" class="form-control" placeholder="Модель">
</div>
<div class="col-md-4">
<div class="d-flex gap-2">
<select class="form-select" id="consModalTypeSelect">
<option value="">Тип...</option>
<option value="Фотобарабан">Фотобарабан</option>
<option value="Ролики захвата бумаги">Ролики захвата бумаги</option>
<option value="Ролики захвата ADF">Ролики захвата ADF</option>
<option value="Термоузел(Печка)">Термоузел(Печка)</option>
<option value="__custom__">Другое...</option>
</select>
<input class="form-control" placeholder="Свой тип" id="consModalTypeCustom">
<input type="hidden" name="type" id="consModalTypeValue">
</div>
</div>
<div class="col-md-2">
<input name="quantity" id="consModalQuantity" 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 rows = Array.from(document.querySelectorAll('[data-type-select]')).map(select => {
const row = select.closest('tr') || select.closest('form') || select.parentElement;
const custom = row.querySelector('[data-type-custom]');
const value = row.querySelector('[data-type-value]');
return { select, custom, value };
});
function syncRow(row) {
const sel = row.select.value;
if (sel === '__custom__') {
row.custom.disabled = false;
row.custom.focus();
row.value.value = (row.custom.value || '').trim();
} else if (sel) {
row.custom.disabled = true;
row.value.value = sel;
} else {
row.custom.disabled = false;
row.value.value = (row.custom.value || '').trim();
}
}
rows.forEach(row => {
row.select.addEventListener('change', () => syncRow(row));
row.custom.addEventListener('input', () => syncRow(row));
// initial state
syncRow(row);
});
})();
</script>
{% if session.get('role') == 'admin' %}
<script>
window.addEventListener('DOMContentLoaded', () => {
const rows = document.querySelectorAll('.js-consumable-row');
if (!rows.length) return;
const modal = document.getElementById('consumableEditModal');
const form = document.getElementById('consumableEditForm');
if (!modal || !form) return;
const modalFields = {
barcode: document.getElementById('consModalBarcode'),
model: document.getElementById('consModalModel'),
quantity: document.getElementById('consModalQuantity'),
typeSelect: document.getElementById('consModalTypeSelect'),
typeCustom: document.getElementById('consModalTypeCustom'),
typeValue: document.getElementById('consModalTypeValue'),
};
function syncType() {
const sel = modalFields.typeSelect.value;
if (sel === '__custom__') {
modalFields.typeCustom.disabled = false;
modalFields.typeValue.value = (modalFields.typeCustom.value || '').trim();
} else if (sel) {
modalFields.typeCustom.disabled = true;
modalFields.typeValue.value = sel;
} else {
modalFields.typeCustom.disabled = false;
modalFields.typeValue.value = (modalFields.typeCustom.value || '').trim();
}
}
function closeModal() {
modal.classList.remove('show');
modal.setAttribute('aria-hidden', 'true');
}
function openModal() {
modal.classList.add('show');
modal.setAttribute('aria-hidden', 'false');
}
function fillModalFromRow(row) {
const barcode = row.getAttribute('data-barcode') || '';
const model = row.getAttribute('data-model') || '';
const ctype = row.getAttribute('data-type') || '';
const quantity = row.getAttribute('data-quantity') || '';
if (!barcode) return false;
modalFields.barcode.value = barcode;
modalFields.model.value = model;
modalFields.quantity.value = quantity;
const known = [
'Фотобарабан',
'Ролики захвата бумаги',
'Ролики захвата ADF',
'Термоузел(Печка)',
];
if (ctype && known.includes(ctype)) {
modalFields.typeSelect.value = ctype;
modalFields.typeCustom.value = '';
} else if (ctype) {
modalFields.typeSelect.value = '__custom__';
modalFields.typeCustom.value = ctype;
} else {
modalFields.typeSelect.value = '';
modalFields.typeCustom.value = '';
}
syncType();
return true;
}
modal.addEventListener('click', (e) => {
if (e.target === modal || e.target.hasAttribute('data-modal-close')) {
closeModal();
}
});
modalFields.typeSelect.addEventListener('change', syncType);
modalFields.typeCustom.addEventListener('input', syncType);
function showActionRow(row) {
const existing = row.parentElement.querySelector('.row-actions');
if (existing) existing.remove();
const barcode = row.getAttribute('data-barcode') || '';
const actionRow = document.createElement('tr');
actionRow.className = 'row-actions';
actionRow.innerHTML = `<td colspan="4">
<div class="row-actions__inner">
<button type="button" class="btn btn-sm btn-outline-primary js-open-cons-modal">Редактировать</button>
<form method="post" action="/consumables/delete" class="d-inline ms-2" onsubmit="return confirm('Удалить расходный материал?')">
<input type="hidden" name="barcode" value="${barcode}">
<button class="btn btn-sm btn-outline-danger" type="submit">Удалить</button>
</form>
</div>
</td>`;
row.insertAdjacentElement('afterend', actionRow);
const btn = actionRow.querySelector('.js-open-cons-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>
{% endif %}
{% endblock %}