Files
Inv_web/templates/projectors.html
2026-03-29 17:05:48 +03:00

604 lines
24 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>
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_projects.png", v=1) }}');
background-size: auto 40vh;
}
.projectors-page .card {
background-color: #B3B3DA;
border: 1px solid #B3B3DA;
box-shadow: none;
}
.projectors-page .card-body {
background-color: transparent;
}
.projectors-page .form-control,
.projectors-page .form-select {
background-color: #ffffff;
border: 1px solid #B3B3DA;
}
.projectors-page .form-control:focus,
.projectors-page .form-select:focus {
box-shadow: 0 0 0 0.1rem rgba(0, 0, 0, 0.15);
border-color: #B3B3DA;
}
.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(1000px, 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;
}
.field-hidden {
display: none;
}
.projector-add-card {
position: relative;
}
.projector-search-btn {
position: absolute;
right: 12px;
bottom: 12px;
}
.projector-summary {
text-align: center;
margin-top: -2px;
margin-bottom: 8px;
color: #5f5f5f;
font-weight: 500;
}
</style>
<div class="projectors-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 projector-add-card">
<div class="card-body">
<h5 class="card-title mb-2">Добавить устройство</h5>
<button class="btn btn-outline-primary projector-search-btn" type="button" id="toggleProjectorSearch" aria-label="Поиск">&#128269;</button>
<form method="post" action="/projectors/add" class="row g-2" id="projectorAddForm">
<div class="col-md-2">
<input name="inventory_number" class="form-control" placeholder="Инв. №">
</div>
<div class="col-md-2">
<select name="kit_type" class="form-select" id="kitTypeSelect">
<option value="">Тип...</option>
{% for value, label in kit_types %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div> <div class="col-md-2 js-field" data-types="kit,projector,board,display,tv">
<select name="brand_select" class="form-select" id="projectorBrandSelect">
<option value="">Бренд...</option>
{% for b in projector_brands %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
<option value="__custom__">+ Добавить бренд</option>
</select>
<input name="brand_custom" id="projectorBrandCustom" class="form-control mt-2 d-none" placeholder="Новый бренд">
<input type="hidden" name="brand" id="projectorBrandValue">
</div>
<div class="col-md-2 js-field" data-types="kit,projector,display,tv">
<input name="projector_model" class="form-control" placeholder="Модель проектора">
</div>
<div class="col-md-2 js-field" data-types="kit,projector,display,tv">
<input name="projector_serial" class="form-control" placeholder="Серийный № проектора">
</div>
<div class="col-md-2 js-field" data-types="kit">
<input name="projector_inventory_number" class="form-control" placeholder="Инв. № проектора">
</div>
<div class="col-md-2 js-field" data-types="kit,board">
<input name="board_model" class="form-control" placeholder="Модель доски">
</div>
<div class="col-md-2 js-field" data-types="kit,board">
<input name="board_serial" class="form-control" placeholder="Серийный № доски">
</div>
<div class="col-md-2 js-field" data-types="kit">
<input name="board_inventory_number" class="form-control" placeholder="Инв. № доски">
</div>
<div class="col-md-3 js-field" data-types="kit,display">
<select name="computer_id" class="form-select" id="projectorComputerSelect">
<option value="">Ноутбук / ПК (модель)...</option>
{% for cid, cinv, cbrand, cmodel, ctype in computers %}
{% set comp_text = [cbrand, cmodel]|select|join(' ') %}
<option value="{{ cid }}" data-inv="{{ cinv }}">{{ comp_text }}{% if cinv %} ({{ cinv }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3 js-field" data-types="kit">
<input name="computer_inventory_number" id="projectorComputerInv" class="form-control" placeholder="Инв. № ноутбука/ПК" readonly>
</div>
<div class="col-md-2 js-field" data-types="kit,projector,board,display,tv">
<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 js-field" data-types="kit,projector,board,display">
<input name="date_in_operation" type="date" class="form-control" placeholder="Дата ввода">
</div>
<div class="col-md-3 js-field" data-types="kit,projector,board,display">
<input name="note" 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="projectorSearchCard">
<div class="card-body">
<form class="row g-2 align-items-center" id="projectorSearchForm">
<div class="col-md-6">
<input id="projectorSearchInput" 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="projectorSearchClear">Очистить</button>
</div>
</form>
</div>
</div>
<table class="table table-striped align-middle">
<tr class="projector-row">
<th>Инв. №</th>
<th>Тип</th>
<th>Бренд</th>
<th>Модель проектора</th>
<th>Серийный №</th>
<th>Модель доски</th>
<th>Серийный № доски</th>
<th>Ноутбук / ПК</th>
<th>Кабинет</th>
<th>Дата ввода</th>
<th>Примечание</th>
<th>Добавлено</th>
</tr>
{% for id, inv, kit_type, brand, proj_model, proj_serial, board_model, board_serial, proj_inv, board_inv, comp_inv_num, comp_id, comp_inv, comp_brand, comp_model, comp_type, cab_id, cab_name, date_in_operation, note, date_added in rows %}
{% set comp_text = [comp_brand, comp_model]|select|join(' ') %}
<tr class="projector-row js-projector-row"
data-id="{{ id }}"
data-inventory-number="{{ inv }}"
data-kit-type="{{ kit_type }}"
data-brand="{{ brand or '' }}"
data-projector-model="{{ proj_model or '' }}"
data-projector-serial="{{ proj_serial or '' }}"
data-board-model="{{ board_model or '' }}"
data-board-serial="{{ board_serial or '' }}"
data-projector-inventory-number="{{ proj_inv or '' }}"
data-board-inventory-number="{{ board_inv or '' }}"
data-computer-inventory-number="{{ comp_inv_num or '' }}"
data-computer-id="{{ comp_id 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 '' }}">
<td>{{ inv }}</td>
<td>{{ kit_type_labels.get(kit_type, kit_type) }}</td>
<td>{{ brand or '' }}</td>
<td>{{ proj_model or '' }}</td>
<td>{{ proj_serial or '' }}</td>
<td>{{ board_model or proj_model or '' }}</td>
<td>{{ board_serial or proj_serial or '' }}</td>
<td>
{% if comp_text %}
{{ comp_text }}{% if comp_inv %} ({{ comp_inv }}){% endif %}
{% else %}
{{ comp_inv or '' }}
{% endif %}
</td>
<td>{{ cab_name or '' }}</td>
<td>{{ date_in_operation or '' }}</td>
<td>{{ note or '' }}</td>
<td>{{ date_added }}</td>
</tr>
{% endfor %}
</table>
<div class="projector-summary">
Всего: {{ total_items }} |
Проекторов: {{ projectors_count }} |
Комплектов: {{ kits_count }} |
Телевизоров: {{ tv_count }} |
Досок: {{ boards_count }} |
Интерактивных экранов: {{ interactive_display_count }}
</div>
{% if session.get('role') in ('admin','storekeeper') %}
<div class="edit-modal" id="projectorEditModal" 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="/projectors/edit" id="projectorEditForm">
<input type="hidden" name="id" id="projectorModalId">
<div class="row g-2">
<div class="col-md-3">
<input name="inventory_number" id="projectorModalInv" class="form-control" placeholder="Инв. №">
</div>
<div class="col-md-3">
<select name="kit_type" id="projectorModalKitType" class="form-select">
<option value="">Тип...</option>
{% for value, label in kit_types %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div> <div class="col-md-3 js-modal-field" data-types="kit,projector,board,display,tv">
<select name="brand_select" id="projectorModalBrandSelect" class="form-select">
<option value="">Бренд...</option>
{% for b in projector_brands %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
<option value="__custom__">+ Добавить бренд</option>
</select>
<input name="brand_custom" id="projectorModalBrandCustom" class="form-control mt-2 d-none" placeholder="Новый бренд">
<input type="hidden" name="brand" id="projectorModalBrand">
</div>
<div class="col-md-3 js-modal-field" data-types="kit,projector,display,tv">
<input name="projector_model" id="projectorModalModel" class="form-control" placeholder="Модель">
</div>
<div class="col-md-3 js-modal-field" data-types="kit,projector,display,tv">
<input name="projector_serial" id="projectorModalSerial" class="form-control" placeholder="Серийный №">
</div>
<div class="col-md-3 js-modal-field" data-types="kit">
<input name="projector_inventory_number" id="projectorModalProjectorInv" class="form-control" placeholder="Инв. № проектора">
</div>
<div class="col-md-3 js-modal-field" data-types="kit,board">
<input name="board_model" id="projectorModalBoard" class="form-control" placeholder="Модель доски">
<input name="board_serial" id="projectorModalBoardSerial" class="form-control mt-2" placeholder="Серийный № доски">
</div>
<div class="col-md-3 js-modal-field" data-types="kit">
<input name="board_inventory_number" id="projectorModalBoardInv" class="form-control" placeholder="Инв. № доски">
</div>
<div class="col-md-4 js-modal-field" data-types="kit,display">
<select name="computer_id" id="projectorModalComputer" class="form-select">
<option value="">Ноутбук / ПК (модель)...</option>
{% for cid, cinv, cbrand, cmodel, ctype in computers %}
{% set comp_text = [cbrand, cmodel]|select|join(' ') %}
<option value="{{ cid }}" data-inv="{{ cinv }}">{{ comp_text }}{% if cinv %} ({{ cinv }}){% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4 js-modal-field" data-types="kit">
<input name="computer_inventory_number" id="projectorModalComputerInv" class="form-control" placeholder="Инв. № ноутбука/ПК" readonly>
</div>
<div class="col-md-3 js-modal-field" data-types="kit,projector,board,display,tv">
<select name="cabinet_id" id="projectorModalCabinet" class="form-select">
<option value="">Кабинет...</option>
{% for cid, cname in cabinets %}
<option value="{{ cid }}">{{ cname }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3 js-modal-field" data-types="kit,projector,board,display">
<input name="date_in_operation" id="projectorModalDateIn" type="date" class="form-control">
</div>
<div class="col-md-6 js-modal-field" data-types="kit,projector,board,display">
<input name="note" id="projectorModalNote" 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 select = document.getElementById('projectorBrandSelect');
const customInput = document.getElementById('projectorBrandCustom');
const valueInput = document.getElementById('projectorBrandValue');
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('projectorModalBrandSelect');
const customInput = document.getElementById('projectorModalBrandCustom');
const valueInput = document.getElementById('projectorModalBrand');
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 kitSelect = document.getElementById('kitTypeSelect');
const addForm = document.getElementById('projectorAddForm');
const compSelect = document.getElementById('projectorComputerSelect');
const compInv = document.getElementById('projectorComputerInv');
function applyType(container, type, selector) {
const fields = Array.from(container.querySelectorAll(selector));
fields.forEach((field) => {
const types = (field.dataset.types || '').split(',').map(t => t.trim()).filter(Boolean);
const show = !types.length || types.includes(type);
field.classList.toggle('field-hidden', !show);
});
}
if (kitSelect && addForm) {
kitSelect.addEventListener('change', () => {
applyType(addForm, kitSelect.value, '.js-field');
});
applyType(addForm, kitSelect.value, '.js-field');
}
if (compSelect && compInv) {
compSelect.addEventListener('change', () => {
const opt = compSelect.options[compSelect.selectedIndex];
const inv = opt ? (opt.getAttribute('data-inv') || '') : '';
compInv.value = inv;
});
}
})();
(function() {
const rows = document.querySelectorAll('.js-projector-row');
if (!rows.length) return;
const modal = document.getElementById('projectorEditModal');
const form = document.getElementById('projectorEditForm');
if (!modal || !form) return;
const modalFields = {
id: document.getElementById('projectorModalId'),
inventory_number: document.getElementById('projectorModalInv'),
kit_type: document.getElementById('projectorModalKitType'),
brand: document.getElementById('projectorModalBrand'),
brand_select: document.getElementById('projectorModalBrandSelect'),
brand_custom: document.getElementById('projectorModalBrandCustom'),
projector_model: document.getElementById('projectorModalModel'),
projector_serial: document.getElementById('projectorModalSerial'),
projector_inventory_number: document.getElementById('projectorModalProjectorInv'),
board_model: document.getElementById('projectorModalBoard'),
board_serial: document.getElementById('projectorModalBoardSerial'),
board_inventory_number: document.getElementById('projectorModalBoardInv'),
computer_inventory_number: document.getElementById('projectorModalComputerInv'),
computer_id: document.getElementById('projectorModalComputer'),
cabinet_id: document.getElementById('projectorModalCabinet'),
date_in_operation: document.getElementById('projectorModalDateIn'),
note: document.getElementById('projectorModalNote'),
};
function closeModal() {
modal.classList.remove('show');
modal.setAttribute('aria-hidden', 'true');
}
function openModal() {
modal.classList.add('show');
modal.setAttribute('aria-hidden', 'false');
}
function applyModalType(type) {
const fields = Array.from(modal.querySelectorAll('.js-modal-field'));
fields.forEach((field) => {
const types = (field.dataset.types || '').split(',').map(t => t.trim()).filter(Boolean);
const show = !types.length || types.includes(type);
field.classList.toggle('field-hidden', !show);
});
}
modal.addEventListener('click', (e) => {
if (e.target === modal || e.target.hasAttribute('data-modal-close')) {
closeModal();
}
});
const kitTypeSelect = document.getElementById('projectorModalKitType');
const modalComputerSelect = document.getElementById('projectorModalComputer');
const modalComputerInv = document.getElementById('projectorModalComputerInv');
if (kitTypeSelect) {
kitTypeSelect.addEventListener('change', () => {
applyModalType(kitTypeSelect.value);
});
}
if (modalComputerSelect && modalComputerInv) {
modalComputerSelect.addEventListener('change', () => {
const opt = modalComputerSelect.options[modalComputerSelect.selectedIndex];
const inv = opt ? (opt.getAttribute('data-inv') || '') : '';
modalComputerInv.value = inv;
});
}
function fillModalFromRow(row) {
const map = [
"id",
"inventory_number",
"kit_type",
"brand",
"projector_model",
"projector_serial",
"projector_inventory_number",
"board_model",
"board_serial",
"board_inventory_number",
"computer_inventory_number",
"computer_id",
"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 (kitTypeSelect) {
applyModalType(kitTypeSelect.value);
}
return filled;
}
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="11">
<div class="row-actions__inner">
<button type="button" class="btn btn-sm btn-outline-primary js-open-projector-modal">Редактировать</button>
<form method="post" action="/projectors/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-projector-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>
<script>
window.addEventListener('DOMContentLoaded', () => {
const toggleBtn = document.getElementById('toggleProjectorSearch');
const card = document.getElementById('projectorSearchCard');
const form = document.getElementById('projectorSearchForm');
const input = document.getElementById('projectorSearchInput');
const clearBtn = document.getElementById('projectorSearchClear');
if (!toggleBtn || !card || !form || !input) return;
const rows = () => document.querySelectorAll('.js-projector-row');
const applyFilter = () => {
const q = (input.value || '').trim().toLowerCase();
rows().forEach((row) => {
const model = (row.getAttribute('data-projector-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>
</div>
{% endblock %}