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

377 lines
13 KiB
HTML

{% extends "base.html" %}
{% block body_class %}page-bg bg-catalog{% endblock %}
{% block content %}
<style>
body.bg-catalog {
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_catalog.png", v=1) }}');
background-size: auto 40vh;
}
.catalog-page .card {
background-color: #B3B3DA;
border: 1px solid #B3B3DA;
box-shadow: none;
}
.catalog-page .card-body {
background-color: transparent;
}
.catalog-page .form-control,
.catalog-page .form-select {
background-color: #ffffff;
border: 1px solid #B3B3DA;
}
.catalog-page .form-control:focus,
.catalog-page .form-select:focus {
box-shadow: 0 0 0 0.1rem rgba(0, 0, 0, 0.15);
border-color: #B3B3DA;
}
.catalog-page .list-group-item {
background-color: transparent;
border: 0;
padding-left: 0;
padding-right: 0;
}
.catalog-page .device-hints {
width: 100%;
background: #ffffff;
border: 1px solid #9499b3;
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.16);
border-radius: 0.25rem;
max-height: 260px;
overflow-y: auto;
}
.catalog-page .device-hints .list-group-item {
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
background: #ffffff;
border: 0;
border-bottom: 1px solid #ececec;
text-align: left;
}
.catalog-page .device-hints .list-group-item:last-child {
border-bottom: 0;
}
.catalog-page .device-hints .list-group-item:hover,
.catalog-page .device-hints .list-group-item:focus {
background: #ecefff;
}
</style>
<div class="catalog-page">
<div class="d-flex align-items-center justify-content-between mb-2">
<h3 class="mb-0">Каталог</h3>
</div>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title mb-2">Подбор картриджа и расходного материала по модели устройства</h5>
<form method="get" action="/catalog" class="row g-2 align-items-center">
<div class="col-md-6 position-relative">
<input type="hidden" name="device" id="deviceHidden" value="{{ selected_key }}">
<input
name="device_display"
class="form-control"
placeholder="Выберите модель..."
value="{{ selected_label }}"
autocomplete="off"
id="deviceInput"
/>
<div id="deviceHints" class="device-hints list-group position-absolute w-100" style="z-index: 10; display: none;"></div>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100">Показать</button>
</div>
</form>
{% if hint %}
<div class="alert alert-warning mt-2 mb-0">{{ hint }}</div>
{% endif %}
</div>
</div>
<script>
(function () {
const input = document.getElementById("deviceInput");
const hidden = document.getElementById("deviceHidden");
const hints = document.getElementById("deviceHints");
const form = input ? input.closest("form") : null;
if (!input || !hidden || !hints) return;
const rows = {{ device_models | tojson }};
const seen = new Set();
const data = rows
.map((r) => {
const brand = (r[0] || "").trim();
const model = (r[1] || "").trim();
const dtype = (r[2] || "").trim();
const typeLabel = dtype === "printer" ? "Принтер" : (dtype === "mfp" ? "МФУ" : dtype);
const base = (brand ? brand + " " : "") + model;
const label = base + (typeLabel ? " (" + typeLabel + ")" : "");
const key = model && dtype ? (model + "||" + dtype) : model;
return { label, key, model, dtype, base };
})
.filter((d) => {
if (!d.model || !d.dtype) return false;
const k = d.label + "||" + d.key;
if (seen.has(k)) return false;
seen.add(k);
return true;
});
function escapeHtml(value) {
return String(value || "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function render(list) {
if (!list.length) {
hints.style.display = "none";
hints.innerHTML = "";
return;
}
hints.innerHTML = list
.map(
(item) =>
'<button type="button" class="list-group-item list-group-item-action" data-key="' +
escapeHtml(item.key) +
'">' +
escapeHtml(item.label) +
"</button>"
)
.join("");
hints.style.display = "block";
}
function showHints() {
const q = input.value.trim().toLowerCase();
const matches = data
.filter((d) => !q || d.label.toLowerCase().includes(q) || d.base.toLowerCase().includes(q))
.slice(0, 10);
render(matches);
}
input.addEventListener("input", () => {
const typed = input.value.trim();
const exact = data.find((d) => d.label.toLowerCase() === typed.toLowerCase());
hidden.value = exact ? exact.key : "";
showHints();
});
hints.addEventListener("click", (e) => {
const btn = e.target.closest("button[data-key]");
if (!btn) return;
const selectedKey = btn.getAttribute("data-key") || "";
const selected = data.find((d) => d.key === selectedKey);
input.value = selected ? selected.label : (btn.textContent || "");
hidden.value = selectedKey;
render([]);
input.focus();
});
input.addEventListener("focus", showHints);
input.addEventListener("click", showHints);
if (form) {
form.addEventListener("submit", () => {
if (hidden.value) return;
const typed = input.value.trim().toLowerCase();
if (!typed) return;
const exact = data.find((d) => d.label.toLowerCase() === typed || d.base.toLowerCase() === typed);
if (exact) {
hidden.value = exact.key;
input.value = exact.label;
return;
}
const firstMatch = data.find((d) => d.label.toLowerCase().includes(typed) || d.base.toLowerCase().includes(typed));
if (firstMatch) {
hidden.value = firstMatch.key;
input.value = firstMatch.label;
}
});
}
input.addEventListener("blur", () => {
setTimeout(() => render([]), 150);
});
})();
</script>
{% if selected_model %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title mb-2">Картриджи для выбранного устройства</h5>
{% if cartridge_rows %}
<div class="list-group">
{% for b, m, q, minq in cartridge_rows %}
<div class="list-group-item">
<div><strong>Модель:</strong> {{ m }}</div>
<div><strong>Остаток:</strong> {{ q }}</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-warning mb-0">Картриджи для выбранной модели не найдены.</div>
{% endif %}
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title mb-2">Расходные материалы для выбранного устройства</h5>
{% if consumable_groups %}
{% for ctype, items in consumable_groups.items() %}
<div class="mb-3">
<div class="fw-semibold mb-2">{{ ctype }}</div>
<div class="list-group">
{% for item in items %}
<div class="list-group-item">
<div><strong>Модель:</strong> {{ item.model or '—' }}</div>
<div><strong>Остаток:</strong> {{ item.qty if item.qty is not none else '—' }}</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<div class="alert alert-warning mb-0">Для выбранного устройства расходники не настроены.</div>
{% endif %}
</div>
</div>
{% endif %}
{% if session.get('role') in ('admin','storekeeper') %}
<div class="card">
<div class="card-body">
<h5 class="card-title mb-2">Настройка соответствия (админ)</h5>
<form method="post" action="/catalog/map" class="row g-2 align-items-center">
<div class="col-md-5">
<select name="device" class="form-select">
<option value="">Модель устройства...</option>
{% for b, m, t in device_models %}
{% set brand = (b ~ " ") if b else "" %}
{% set label = (brand ~ m ~ " (" ~ ("Принтер" if t == "printer" else "МФУ") ~ ")") %}
{% set key = m ~ "||" ~ t %}
<option value="{{ key }}" {% if selected_key == key %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-5">
<select name="cartridge_model" class="form-select">
<option value="">Модель картриджа...</option>
{% for m in cartridges %}
<option value="{{ m }}" {% if cartridge_model == m %}selected{% endif %}>{{ m }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2 d-grid">
<button class="btn btn-success">Сохранить</button>
</div>
</form>
</div>
</div>
<div class="card mt-3">
<div class="card-body">
<h5 class="card-title mb-2">Расходники для устройств (админ)</h5>
<form method="post" action="/catalog/consumables/map" class="row g-2 align-items-center">
<div class="col-md-4">
<select name="device" class="form-select">
<option value="">Модель устройства...</option>
{% for b, m, t in device_models %}
{% set brand = (b ~ " ") if b else "" %}
{% set label = (brand ~ m ~ " (" ~ ("Принтер" if t == "printer" else "МФУ") ~ ")") %}
{% set key = m ~ "||" ~ t %}
<option value="{{ key }}" {% if selected_key == key %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<div class="d-flex gap-2">
<select class="form-select" data-consumable-type-select>
<option value="">Тип расходника...</option>
{% for t in consumable_types %}
<option value="{{ t }}">{{ t }}</option>
{% endfor %}
<option value="__custom__">Другое...</option>
</select>
<input class="form-control" placeholder="Свой тип" data-consumable-type-custom>
<input type="hidden" name="consumable_type" data-consumable-type-value>
</div>
</div>
<div class="col-md-3">
<select name="consumable_barcode" class="form-select" data-consumable-barcode-select>
<option value="">Расходный материал...</option>
{% for b, m, t, q in consumables %}
<option value="{{ b }}" data-consumable-type="{{ t }}">{{ m }} ({{ b }})</option>
{% endfor %}
</select>
</div>
<div class="col-md-2 d-grid">
<button class="btn btn-success">Сохранить</button>
</div>
</form>
</div>
</div>
{% endif %}
<script>
(function() {
const select = document.querySelector('[data-consumable-type-select]');
if (!select) return;
const custom = document.querySelector('[data-consumable-type-custom]');
const value = document.querySelector('[data-consumable-type-value]');
const barcodeSelect = document.querySelector('[data-consumable-barcode-select]');
function sync() {
const sel = select.value;
if (sel === '__custom__') {
custom.disabled = false;
value.value = (custom.value || '').trim();
} else if (sel) {
custom.disabled = true;
value.value = sel;
} else {
custom.disabled = false;
value.value = (custom.value || '').trim();
}
filterConsumables();
}
function filterConsumables() {
if (!barcodeSelect) return;
const selectedType = (value.value || '').trim();
const normSelected = selectedType.toLowerCase();
const options = Array.from(barcodeSelect.options);
let hasMatch = false;
options.forEach((opt, idx) => {
if (idx === 0) return; // placeholder
const optType = (opt.getAttribute('data-consumable-type') || '').trim();
const normOpt = optType.toLowerCase();
const show = !selectedType || normOpt === normSelected;
opt.hidden = !show;
opt.disabled = !show;
if (show) hasMatch = true;
});
if (!hasMatch) {
barcodeSelect.value = '';
}
}
select.addEventListener('change', sync);
custom.addEventListener('input', sync);
sync();
})();
</script>
</div>
{% endblock %}