320 lines
9.9 KiB
HTML
320 lines
9.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block body_class %}page-bg bg-stock{% endblock %}
|
|
{% block content %}
|
|
<style>
|
|
body.bg-stock {
|
|
background-color: #B3B3DA;
|
|
background-image:
|
|
linear-gradient(rgba(179, 179, 218, 0.75), rgba(179, 179, 218, 0.75)),
|
|
url('{{ url_for("static", filename="bg/bg_sklad.png", v=1) }}');
|
|
background-size: auto 40vh;
|
|
}
|
|
.components-wrap {
|
|
background: transparent;
|
|
border: 0;
|
|
padding: 0;
|
|
}
|
|
.components-form-wrap {
|
|
margin-bottom: 0.9rem;
|
|
}
|
|
.components-form {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr 0.7fr 0.9fr auto;
|
|
gap: 0.6rem;
|
|
align-items: end;
|
|
margin-bottom: 0.9rem;
|
|
background: transparent;
|
|
border: 0;
|
|
box-shadow: none;
|
|
padding: 0;
|
|
}
|
|
@media (max-width: 1100px) {
|
|
.components-form {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
@media (max-width: 640px) {
|
|
.components-form {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
.components-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
background: #fff;
|
|
}
|
|
.components-table th,
|
|
.components-table td {
|
|
border: 1px solid #b8b8b8;
|
|
padding: 0.45rem 0.55rem;
|
|
font-size: 0.92rem;
|
|
vertical-align: middle;
|
|
}
|
|
.components-table th {
|
|
background: #d9d9d9;
|
|
font-weight: 600;
|
|
}
|
|
.components-empty {
|
|
text-align: center;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
.edit-modal {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.45);
|
|
z-index: 1000;
|
|
}
|
|
.edit-modal.show {
|
|
display: flex;
|
|
}
|
|
.edit-modal__dialog {
|
|
width: min(760px, 95vw);
|
|
background: #fff;
|
|
border-radius: 10px;
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
|
padding: 16px;
|
|
}
|
|
.edit-modal__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
.edit-modal__title {
|
|
margin: 0;
|
|
font-size: 1.05rem;
|
|
font-weight: 600;
|
|
}
|
|
.edit-modal__footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
margin-top: 12px;
|
|
}
|
|
.row-actions {
|
|
background: #f3f3f3;
|
|
}
|
|
.row-actions__inner {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
padding: 0.45rem 0.55rem;
|
|
align-items: center;
|
|
max-height: 0;
|
|
opacity: 0;
|
|
overflow: hidden;
|
|
transform: translateY(-4px);
|
|
transition: max-height 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
|
|
}
|
|
.row-actions.show .row-actions__inner {
|
|
max-height: 80px;
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
</style>
|
|
|
|
<h3>Компьютерные комплектующие</h3>
|
|
{% if session.get('role') in ('admin', 'storekeeper') %}
|
|
<div class="components-form-wrap">
|
|
<form method="post" action="/stock/components/add" class="components-form">
|
|
<div>
|
|
<label class="form-label">Штрихкод</label>
|
|
<input class="form-control" name="barcode" placeholder="Необязательно">
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Модель</label>
|
|
<input class="form-control" name="model" required>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Тип</label>
|
|
<select class="form-select" name="component_type" required>
|
|
<option value="">Выберите тип</option>
|
|
{% for t in component_types %}
|
|
<option value="{{ t }}">{{ t }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Кол-во</label>
|
|
<input class="form-control" name="quantity" type="number" min="1" value="1" required>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Состояние</label>
|
|
<select class="form-select" name="item_condition" required>
|
|
{% for st in component_conditions %}
|
|
<option value="{{ st }}">{{ st }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-primary">Добавить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="components-wrap">
|
|
<table class="components-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Штрихкод</th>
|
|
<th>Модель</th>
|
|
<th>Тип</th>
|
|
<th>Кол-во</th>
|
|
<th>Состояние</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if rows %}
|
|
{% for r in rows %}
|
|
<tr class="js-components-row"
|
|
data-id="{{ r[0] }}"
|
|
data-barcode="{{ r[1] or '' }}"
|
|
data-model="{{ r[2] or '' }}"
|
|
data-type="{{ r[3] or '' }}"
|
|
data-quantity="{{ r[4] }}"
|
|
data-condition="{{ r[5] or '' }}">
|
|
<td>{{ r[1] or '—' }}</td>
|
|
<td>{{ r[2] }}</td>
|
|
<td>{{ r[3] }}</td>
|
|
<td>{{ r[4] }}</td>
|
|
<td>{{ r[5] }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% else %}
|
|
<tr>
|
|
<td class="components-empty" colspan="5">Записей пока нет</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if session.get('role') in ('admin', 'storekeeper') %}
|
|
<div class="edit-modal" id="componentsEditModal" 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="/stock/components/edit" id="componentsEditForm">
|
|
<input type="hidden" name="id" id="componentsModalId">
|
|
<div class="row g-2">
|
|
<div class="col-md-3">
|
|
<input name="barcode" id="componentsModalBarcode" class="form-control" placeholder="Штрихкод">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<input name="model" id="componentsModalModel" class="form-control" placeholder="Модель" required>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select name="component_type" id="componentsModalType" class="form-select" required>
|
|
<option value="">Тип...</option>
|
|
{% for t in component_types %}
|
|
<option value="{{ t }}">{{ t }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<input name="quantity" id="componentsModalQty" class="form-control" placeholder="Кол-во">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="item_condition" id="componentsModalCondition" class="form-select" required>
|
|
<option value="">Состояние...</option>
|
|
{% for st in component_conditions %}
|
|
<option value="{{ st }}">{{ st }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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('.js-components-row'));
|
|
const modal = document.getElementById('componentsEditModal');
|
|
const modalFields = {
|
|
id: document.getElementById('componentsModalId'),
|
|
barcode: document.getElementById('componentsModalBarcode'),
|
|
model: document.getElementById('componentsModalModel'),
|
|
type: document.getElementById('componentsModalType'),
|
|
qty: document.getElementById('componentsModalQty'),
|
|
condition: document.getElementById('componentsModalCondition'),
|
|
};
|
|
|
|
if (!rows.length || !modal) return;
|
|
|
|
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 id = row.getAttribute('data-id') || '';
|
|
if (!id) return false;
|
|
modalFields.id.value = id;
|
|
modalFields.barcode.value = row.getAttribute('data-barcode') || '';
|
|
modalFields.model.value = row.getAttribute('data-model') || '';
|
|
modalFields.type.value = row.getAttribute('data-type') || '';
|
|
modalFields.qty.value = row.getAttribute('data-quantity') || '1';
|
|
modalFields.condition.value = row.getAttribute('data-condition') || '';
|
|
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="5">
|
|
<div class="row-actions__inner">
|
|
<button type="button" class="btn btn-sm btn-outline-primary js-open-components-modal">Редактировать</button>
|
|
<form method="post" action="/stock/components/delete" class="d-inline ms-2" onsubmit="return confirm('Удалить запись?')">
|
|
<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-components-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 %}
|