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

713 lines
26 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;
}
.stock-section {
background: #B3B3DA;
border: 1px solid #B3B3DA;
box-shadow: none;
padding: 0.75rem;
margin-bottom: 1rem;
}
.stock-page .form-control,
.stock-page .form-select {
background-color: #ffffff;
border: 1px solid #B3B3DA;
}
.stock-page .form-control:focus,
.stock-page .form-select:focus {
box-shadow: 0 0 0 0.1rem rgba(0, 0, 0, 0.15);
border-color: #B3B3DA;
}
.stock-section h4 {
margin: 0 0 0.5rem 0;
font-size: 1rem;
}
.stock-form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.6rem 1rem;
}
.stock-add-grid {
display: grid;
grid-template-columns: 1.2fr 0.9fr 1.2fr 1fr 1.2fr;
gap: 0.6rem 1rem;
grid-template-areas:
"inv type brand model serial"
"condition condition condition status status";
align-items: end;
}
.stock-add-field {
min-width: 0;
}
.stock-add-inv { grid-area: inv; }
.stock-add-type { grid-area: type; }
.stock-add-brand { grid-area: brand; }
.stock-add-model { grid-area: model; }
.stock-add-serial { grid-area: serial; }
.stock-add-condition { grid-area: condition; }
.stock-add-status { grid-area: status; }
@media (max-width: 980px) {
.stock-add-grid {
grid-template-columns: 1fr 1fr;
grid-template-areas:
"inv inv"
"type brand"
"model serial"
"condition condition"
"status status";
}
}
@media (max-width: 620px) {
.stock-add-grid {
grid-template-columns: 1fr;
grid-template-areas:
"inv"
"type"
"brand"
"model"
"serial"
"condition"
"status";
}
}
.stock-form-grid .full {
grid-column: 1 / -1;
}
.stock-add-grid .full {
grid-column: 1 / -1;
}
.stock-form-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.stock-table {
width: 100%;
border-collapse: collapse;
}
.stock-table th,
.stock-table td {
border: 1px solid #b5b5b5;
padding: 0.35rem 0.5rem;
font-size: 0.92rem;
}
.stock-table th {
background: #d9d9d9;
font-weight: 600;
}
.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;
}
.edit-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e2e2e2;
}
.edit-modal__title {
margin: 0;
font-size: 1.05rem;
}
.edit-modal__footer {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-top: 1px solid #e2e2e2;
}
.stock-note {
font-size: 0.85rem;
color: #555;
}
.specs-panel {
display: none;
margin-top: 0.6rem;
padding: 0;
border: none;
background: transparent;
}
.specs-panel.active {
display: block;
}
.stock-specs-grid {
display: grid;
grid-template-columns: 1.05fr 1.05fr 1.05fr 0.9fr 0.9fr 0.9fr;
gap: 0.6rem 1rem;
align-items: end;
}
@media (max-width: 980px) {
.stock-specs-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 620px) {
.stock-specs-grid {
grid-template-columns: 1fr;
}
}
</style>
<div class="stock-page">
<h3>{{ stock_view_title or 'Склад' }}</h3>
<div class="stock-section">
<h4>Добавить запись</h4>
<form method="post" action="/stock/add">
<div class="stock-add-grid">
<div class="stock-add-field stock-add-inv">
<label class="form-label">Инвентарный номер</label>
<input class="form-control" name="inventory_number">
</div>
<div class="stock-add-field stock-add-type">
<label class="form-label">Тип</label>
<select class="form-select" name="type" id="stockType">
<option value="">Выберите тип</option>
<option value="pc">ПК/Ноутбуки</option>
<option value="printers">МФУ/Принтеры</option>
<option value="projectors">Проекторы</option>
<option value="boards">Доски</option>
<option value="doc_cameras">Документ-камеры</option>
<option value="other">Остальное</option>
</select>
</div>
<div class="stock-add-field stock-add-brand">
<label class="form-label">Бренд</label>
<input class="form-control" name="brand">
</div>
<div class="stock-add-field stock-add-model">
<label class="form-label">Модель</label>
<input class="form-control" name="model">
</div>
<div class="stock-add-field stock-add-serial">
<label class="form-label">Серийный номер</label>
<input class="form-control" name="serial_number">
</div>
<div class="stock-add-field stock-add-condition">
<label class="form-label">Состояние</label>
<input class="form-control" name="condition">
</div>
<div class="stock-add-field stock-add-status">
<label class="form-label">Статус</label>
<select class="form-select" name="status">
<option value="">Выберите статус</option>
<option value="Действующий">Действующий</option>
<option value="Списан">Списан</option>
</select>
</div>
<div class="full">
<button type="button" class="btn btn-sm btn-outline-secondary" id="specsBtn" style="display:none;">
Характеристики
</button>
<div class="specs-panel" id="specsPanel">
<div class="stock-specs-grid">
<div>
<label class="form-label">Бренд CPU</label>
<select class="form-select" name="cpu_brand">
<option value="">Выберите бренд</option>
<option value="Intel">Intel</option>
<option value="AMD">AMD</option>
</select>
</div>
<div>
<label class="form-label">CPU</label>
<input class="form-control" name="cpu_model">
</div>
<div>
<label class="form-label">GPU</label>
<input class="form-control" name="gpu_model">
<input type="hidden" name="gpu_brand" value="">
</div>
<div>
<label class="form-label">Объем памяти</label>
<input class="form-control" name="memory_size">
</div>
<div>
<label class="form-label">Тип памяти</label>
<select class="form-select" name="memory_type">
<option value="">Выберите тип</option>
<option value="DDR">DDR</option>
<option value="DDR-2">DDR-2</option>
<option value="DDR-3">DDR-3</option>
<option value="DDR-4">DDR-4</option>
<option value="DDR-5">DDR-5</option>
</select>
</div>
<div>
<label class="form-label">Объем накопителя</label>
<input class="form-control" name="storage_size">
</div>
</div>
</div>
</div>
</div>
<div class="stock-form-actions">
<button class="btn btn-sm btn-primary" type="submit">Добавить</button>
</div>
</form>
</div>
<div class="stock-section">
<h4>ПК/Ноутбуки</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('pc', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>ПК/Ноутбуки</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
<div class="stock-section">
<h4>МФУ/Принтеры</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('printers', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>МФУ/Принтеры</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
<div class="stock-section">
<h4>Проекторы</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('projectors', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>Проекторы</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
<div class="stock-section">
<h4>Доски</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('boards', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>Доски</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
<div class="stock-section">
<h4>Документ-камеры</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('doc_cameras', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>Документ-камеры</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
<div class="stock-section">
<h4>Остальное</h4>
<table class="stock-table">
<tr><th>Инвентарный номер</th><th>Тип</th><th>Бренд</th><th>Модель</th><th>Серийный номер</th><th>Состояние</th><th>Статус</th></tr>
{% for sid, inv, itype, brand, model, serial, condition, status, cpu_brand, cpu_model, gpu_brand, gpu_model, memory_size, memory_type, storage_size in items.get('other', []) %}
<tr class="js-stock-row"
data-id="{{ sid }}"
data-inventory-number="{{ inv or '' }}"
data-type="{{ itype }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ serial or '' }}"
data-condition="{{ condition or '' }}"
data-status="{{ status or '' }}"
data-cpu-brand="{{ cpu_brand or '' }}"
data-cpu-model="{{ cpu_model or '' }}"
data-gpu-brand="{{ gpu_brand or '' }}"
data-gpu-model="{{ gpu_model or '' }}"
data-memory-size="{{ memory_size or '' }}"
data-memory-type="{{ memory_type or '' }}"
data-storage-size="{{ storage_size or '' }}">
<td>{{ inv }}</td>
<td>Остальное</td>
<td>{{ brand }}</td>
<td>{{ model }}</td>
<td>{{ serial }}</td>
<td>{{ condition }}</td>
<td>{{ status }}</td>
</tr>
{% else %}
<tr><td colspan="7" class="stock-note">Записей нет</td></tr>
{% endfor %}
</table>
</div>
{% if session.get('role') in ('admin','storekeeper') %}
<div class="edit-modal" id="stockEditModal" 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/edit" id="stockEditForm">
<input type="hidden" name="id" id="stockModalId">
<div class="row g-2">
<div class="col-md-3">
<input name="inventory_number" id="stockModalInv" class="form-control" placeholder="Инв. №">
</div>
<div class="col-md-3">
<select name="type" id="stockModalType" class="form-select">
<option value="">Тип...</option>
<option value="pc">ПК/Ноутбуки</option>
<option value="printers">МФУ/Принтеры</option>
<option value="projectors">Проекторы</option>
<option value="boards">Доски</option>
<option value="doc_cameras">Документ-камеры</option>
<option value="other">Остальное</option>
</select>
</div>
<div class="col-md-3">
<input name="brand" id="stockModalBrand" class="form-control" placeholder="Бренд">
</div>
<div class="col-md-3">
<input name="model" id="stockModalModel" class="form-control" placeholder="Модель">
</div>
<div class="col-md-3">
<input name="serial_number" id="stockModalSerial" class="form-control" placeholder="Серийный №">
</div>
<div class="col-md-3">
<input name="condition" id="stockModalCondition" class="form-control" placeholder="Состояние">
</div>
<div class="col-md-3">
<select name="status" id="stockModalStatus" class="form-select">
<option value="">Статус...</option>
<option value="Действующий">Действующий</option>
<option value="Списан">Списан</option>
</select>
</div>
<div class="col-md-3">
<select name="cpu_brand" id="stockModalCpuBrand" class="form-select">
<option value="">Бренд CPU...</option>
<option value="Intel">Intel</option>
<option value="AMD">AMD</option>
</select>
</div>
<div class="col-md-3">
<input name="cpu_model" id="stockModalCpuModel" class="form-control" placeholder="CPU">
</div>
<div class="col-md-3">
<input name="gpu_model" id="stockModalGpuModel" class="form-control" placeholder="GPU">
<input type="hidden" name="gpu_brand" id="stockModalGpuBrand">
</div>
<div class="col-md-3">
<input name="memory_size" id="stockModalMemorySize" class="form-control" placeholder="Память">
</div>
<div class="col-md-3">
<select name="memory_type" id="stockModalMemoryType" class="form-select">
<option value="">Тип памяти...</option>
<option value="DDR">DDR</option>
<option value="DDR-2">DDR-2</option>
<option value="DDR-3">DDR-3</option>
<option value="DDR-4">DDR-4</option>
<option value="DDR-5">DDR-5</option>
</select>
</div>
<div class="col-md-3">
<input name="storage_size" id="stockModalStorageSize" 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 typeSelect = document.getElementById('stockType');
const specsBtn = document.getElementById('specsBtn');
const specsPanel = document.getElementById('specsPanel');
if (!typeSelect || !specsBtn || !specsPanel) return;
function syncSpecs() {
const isPc = typeSelect.value === 'pc';
specsBtn.style.display = isPc ? 'inline-block' : 'none';
if (!isPc) specsPanel.classList.remove('active');
}
specsBtn.addEventListener('click', () => {
specsPanel.classList.toggle('active');
});
typeSelect.addEventListener('change', syncSpecs);
syncSpecs();
})();
</script>
<script>
(function() {
const canEdit = {{ 'true' if session.get('role') in ('admin','storekeeper') else 'false' }};
if (!canEdit) return;
const rows = document.querySelectorAll('.js-stock-row');
if (!rows.length) return;
const modal = document.getElementById('stockEditModal');
const form = document.getElementById('stockEditForm');
if (!modal || !form) return;
const modalFields = {
id: document.getElementById('stockModalId'),
inventory_number: document.getElementById('stockModalInv'),
type: document.getElementById('stockModalType'),
brand: document.getElementById('stockModalBrand'),
model: document.getElementById('stockModalModel'),
serial_number: document.getElementById('stockModalSerial'),
condition: document.getElementById('stockModalCondition'),
status: document.getElementById('stockModalStatus'),
cpu_brand: document.getElementById('stockModalCpuBrand'),
cpu_model: document.getElementById('stockModalCpuModel'),
gpu_brand: document.getElementById('stockModalGpuBrand'),
gpu_model: document.getElementById('stockModalGpuModel'),
memory_size: document.getElementById('stockModalMemorySize'),
memory_type: document.getElementById('stockModalMemoryType'),
storage_size: document.getElementById('stockModalStorageSize'),
};
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",
"type",
"brand",
"model",
"serial_number",
"condition",
"status",
"cpu_brand",
"cpu_model",
"gpu_brand",
"gpu_model",
"memory_size",
"memory_type",
"storage_size",
];
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 || '';
}
});
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="7">
<div class="row-actions__inner">
<button type="button" class="btn btn-sm btn-outline-primary js-open-stock-modal">Редактировать</button>
<form method="post" action="/stock/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-stock-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>
</div>
{% endblock %}