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

430 lines
15 KiB
HTML

{% extends "base.html" %}
{% block body_class %}page-bg bg-devices{% endblock %}
{% block content %}
<style>
body.bg-devices {
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_doccams.png", v=1) }}');
background-size: auto 40vh;
}
.doccams-page .card {
background-color: #B3B3DA;
border: 1px solid #B3B3DA;
box-shadow: none;
}
.doccams-page .card-body {
background-color: transparent;
}
.doccams-page .form-control,
.doccams-page .form-select {
background-color: #ffffff;
border: 1px solid #B3B3DA;
}
.doccams-page .form-control:focus,
.doccams-page .form-select:focus {
box-shadow: 0 0 0 0.1rem rgba(0, 0, 0, 0.15);
border-color: #B3B3DA;
}
.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;
}
.doc-add-card {
position: relative;
}
.doc-search-btn {
position: absolute;
right: 12px;
bottom: 12px;
}
</style>
<div class="doccams-page">
<div class="d-flex align-items-center justify-content-between mb-2">
<h3 class="mb-0">Документ-камеры</h3>
</div>
{% if session.get('role') not in ('admin','storekeeper') %}
<div class="alert alert-secondary">Режим просмотра: изменения доступны только администратору.</div>
{% endif %}
{% if session.get('role') in ('admin','storekeeper') %}
<div class="card mb-3 doc-add-card">
<div class="card-body">
<h5 class="card-title mb-2">Добавить документ-камеру</h5>
<button class="btn btn-outline-primary doc-search-btn" type="button" id="toggleDocSearch" aria-label="Поиск">&#128269;</button>
<form method="post" action="/document-cameras/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_select" class="form-select" id="documentCameraBrandSelect">
<option value="">Бренд...</option>
{% for b in document_camera_brands %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
<option value="__custom__">+ Добавить бренд</option>
</select>
<input name="brand_custom" id="documentCameraBrandCustom" class="form-control mt-2 d-none" placeholder="Новый бренд">
<input type="hidden" name="brand" id="documentCameraBrandValue">
</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="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-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 %}
<div class="card mb-3 d-none" id="docSearchCard">
<div class="card-body">
<form class="row g-2 align-items-center" id="docSearchForm">
<div class="col-md-6">
<input id="docSearchInput" class="form-control" placeholder="Модель документ-камеры">
</div>
<div class="col-md-2 d-grid">
<button class="btn btn-primary" type="submit">Найти</button>
</div>
<div class="col-md-2 d-grid">
<button class="btn btn-outline-secondary" type="button" id="docSearchClear">Очистить</button>
</div>
</form>
</div>
</div>
<table class="table table-striped align-middle">
<tr>
<th>Инв. №</th>
<th>Бренд</th>
<th>Модель</th>
<th>Серийный №</th>
<th>Кабинет</th>
<th>Дата ввода в эксплуатацию</th>
</tr>
{% for id, inv, brand, model, sn, cab_id, cab_name, date_in_operation, date_added in rows %}
<tr class="js-doc-row"
data-id="{{ id }}"
data-inventory-number="{{ inv }}"
data-brand="{{ brand or '' }}"
data-model="{{ model or '' }}"
data-serial-number="{{ sn or '' }}"
data-cabinet-id="{{ cab_id or '' }}"
data-date-in-operation="{{ date_in_operation.strftime('%Y-%m-%d') if date_in_operation else '' }}">
<td>{{ inv }}</td>
<td>{{ brand or '' }}</td>
<td>{{ model or '' }}</td>
<td>{{ sn or '' }}</td>
<td>{{ cab_name or '' }}</td>
<td>{{ date_in_operation or '' }}</td>
</tr>
{% endfor %}
</table>
{% if session.get('role') in ('admin','storekeeper') %}
<div class="edit-modal" id="docEditModal" 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="/document-cameras/edit" id="docEditForm">
<input type="hidden" name="id" id="docModalId">
<div class="row g-2">
<div class="col-md-3">
<input name="inventory_number" id="docModalInv" class="form-control" placeholder="Инв. №">
</div>
<div class="col-md-3">
<select name="brand_select" id="documentCameraModalBrandSelect" class="form-select">
<option value="">Бренд...</option>
{% for b in document_camera_brands %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
<option value="__custom__">+ Добавить бренд</option>
</select>
<input name="brand_custom" id="documentCameraModalBrandCustom" class="form-control mt-2 d-none" placeholder="Новый бренд">
<input type="hidden" name="brand" id="docModalBrand">
</div>
<div class="col-md-3">
<input name="model" id="docModalModel" class="form-control" placeholder="Модель">
</div>
<div class="col-md-3">
<input name="serial_number" id="docModalSerial" class="form-control" placeholder="Серийный №">
</div>
<div class="col-md-4">
<select name="cabinet_id" id="docModalCabinet" class="form-select">
<option value="">Кабинет...</option>
{% for cid, cname in cabinets %}
<option value="{{ cid }}">{{ cname }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<input name="date_in_operation" id="docModalDateIn" type="date" class="form-control">
</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 %}
{% if session.get('role') in ('admin','storekeeper') %}
<script>
(function() {
const select = document.getElementById('documentCameraBrandSelect');
const customInput = document.getElementById('documentCameraBrandCustom');
const valueInput = document.getElementById('documentCameraBrandValue');
if (!select || !customInput || !valueInput) return;
function syncBrand() {
if (select.value === '__custom__') {
customInput.classList.remove('d-none');
valueInput.value = customInput.value.trim();
} else {
customInput.classList.add('d-none');
customInput.value = '';
valueInput.value = select.value;
}
}
select.addEventListener('change', syncBrand);
customInput.addEventListener('input', syncBrand);
syncBrand();
})();
(function() {
const select = document.getElementById('documentCameraModalBrandSelect');
const customInput = document.getElementById('documentCameraModalBrandCustom');
const valueInput = document.getElementById('docModalBrand');
if (!select || !customInput || !valueInput) return;
function syncBrand() {
if (select.value === '__custom__') {
customInput.classList.remove('d-none');
valueInput.value = customInput.value.trim();
} else {
customInput.classList.add('d-none');
customInput.value = '';
valueInput.value = select.value;
}
}
select.addEventListener('change', syncBrand);
customInput.addEventListener('input', syncBrand);
syncBrand();
})();
(function() {
const rows = document.querySelectorAll('.js-doc-row');
if (!rows.length) return;
const modal = document.getElementById('docEditModal');
const form = document.getElementById('docEditForm');
if (!modal || !form) return;
const fields = {
id: document.getElementById('docModalId'),
inventory_number: document.getElementById('docModalInv'),
brand: document.getElementById('docModalBrand'),
brand_select: document.getElementById('documentCameraModalBrandSelect'),
brand_custom: document.getElementById('documentCameraModalBrandCustom'),
model: document.getElementById('docModalModel'),
serial_number: document.getElementById('docModalSerial'),
cabinet_id: document.getElementById('docModalCabinet'),
date_in_operation: document.getElementById('docModalDateIn'),
};
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 fillModal(row) {
const map = {
id: 'id',
inventory_number: 'inventory-number',
brand: 'brand',
model: 'model',
serial_number: 'serial-number',
cabinet_id: 'cabinet-id',
date_in_operation: 'date-in-operation',
};
Object.keys(map).forEach((key) => {
const attr = row.getAttribute(`data-${map[key]}`) || '';
if (fields[key]) fields[key].value = attr;
});
const modalBrandSelect = document.getElementById('documentCameraModalBrandSelect');
const modalBrandCustom = document.getElementById('documentCameraModalBrandCustom');
const modalBrandValue = document.getElementById('docModalBrand');
if (modalBrandSelect && modalBrandValue) {
const current = modalBrandValue.value || '';
if (current && Array.from(modalBrandSelect.options).some(o => o.value === current)) {
modalBrandSelect.value = current;
if (modalBrandCustom) {
modalBrandCustom.classList.add('d-none');
modalBrandCustom.value = '';
}
} else {
modalBrandSelect.value = current ? '__custom__' : '';
if (modalBrandCustom) {
modalBrandCustom.classList.toggle('d-none', !current);
modalBrandCustom.value = current;
}
}
}
}
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="6">
<div class="row-actions__inner">
<button type="button" class="btn btn-sm btn-outline-primary js-open-doc-modal">Редактировать</button>
<form method="post" action="/document-cameras/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-doc-modal');
btn.addEventListener('click', () => {
fillModal(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;
fillModal(row);
openModal();
});
});
})();
</script>
<script>
window.addEventListener('DOMContentLoaded', () => {
const toggleBtn = document.getElementById('toggleDocSearch');
const card = document.getElementById('docSearchCard');
const form = document.getElementById('docSearchForm');
const input = document.getElementById('docSearchInput');
const clearBtn = document.getElementById('docSearchClear');
if (!toggleBtn || !card || !form || !input) return;
const rows = () => document.querySelectorAll('.js-doc-row');
const applyFilter = () => {
const q = (input.value || '').trim().toLowerCase();
rows().forEach((row) => {
const model = (row.getAttribute('data-model') || '').toLowerCase();
const match = !q || model.includes(q);
row.classList.toggle('d-none', !match);
});
};
toggleBtn.addEventListener('click', () => {
card.classList.toggle('d-none');
if (!card.classList.contains('d-none')) input.focus();
});
form.addEventListener('submit', (e) => {
e.preventDefault();
applyFilter();
});
if (clearBtn) {
clearBtn.addEventListener('click', () => {
input.value = '';
applyFilter();
});
}
});
</script>
{% endif %}
</div>
{% endblock %}