3883 lines
174 KiB
HTML
3883 lines
174 KiB
HTML
{% extends "base.html" %}
|
||
{% block body_class %}bg-home{% endblock %}
|
||
{% block content %}
|
||
<style>
|
||
.login-reveal {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 2000;
|
||
pointer-events: none;
|
||
background:
|
||
radial-gradient(800px 500px at 20% 20%, rgba(99, 102, 241, 0.22), transparent 60%),
|
||
radial-gradient(700px 460px at 85% 25%, rgba(14, 165, 233, 0.18), transparent 62%),
|
||
linear-gradient(135deg, rgba(10, 14, 20, 0.98), rgba(6, 10, 16, 0.98));
|
||
opacity: 0;
|
||
animation: revealFadeIn 140ms ease forwards;
|
||
}
|
||
.login-reveal__scanlines {
|
||
position: absolute;
|
||
inset: 0;
|
||
background-image: repeating-linear-gradient(
|
||
to bottom,
|
||
rgba(226, 232, 240, 0.06) 0 1px,
|
||
rgba(15, 23, 32, 0) 1px 4px
|
||
);
|
||
opacity: 0;
|
||
animation: scanlinesOn 1.1s ease-out forwards;
|
||
}
|
||
.login-reveal__vignette {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(circle at 50% 45%, transparent 55%, rgba(6, 10, 16, 0.65) 85%);
|
||
opacity: 0;
|
||
animation: vignetteIn 1s ease-out forwards;
|
||
}
|
||
.login-reveal__glint {
|
||
position: absolute;
|
||
left: -20%;
|
||
top: 0;
|
||
width: 140%;
|
||
height: 100%;
|
||
background: linear-gradient(110deg, transparent 0%, rgba(99, 102, 241, 0.12) 45%, rgba(125, 211, 252, 0.5) 50%, rgba(99, 102, 241, 0.12) 55%, transparent 100%);
|
||
transform: translateY(10%) translateX(-30%) rotate(-8deg);
|
||
animation: glintSweep 0.9s ease-out forwards;
|
||
}
|
||
.login-reveal__beam {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 260px;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, transparent, rgba(125, 211, 252, 0.8), transparent);
|
||
transform: translate(-50%, -50%);
|
||
opacity: 0;
|
||
animation: beamPulse 1s ease-out forwards;
|
||
}
|
||
.login-reveal--out {
|
||
animation: revealFadeOut 320ms ease forwards;
|
||
}
|
||
.home-wrap .reveal-item {
|
||
opacity: 0;
|
||
transform: translateY(14px) scale(0.995);
|
||
animation: contentRise 480ms ease forwards;
|
||
}
|
||
.home-top-action {
|
||
position: fixed;
|
||
right: 8px;
|
||
top: 40px;
|
||
z-index: 1100;
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
}
|
||
.home-top-action__stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.home-note {
|
||
position: fixed;
|
||
left: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: #fff6a8;
|
||
color: #111;
|
||
border: 1px solid #000;
|
||
padding: 6px 8px;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.9rem;
|
||
box-shadow: inset 1px 1px 0 #fffbd8, inset -1px -1px 0 #b7ad62;
|
||
max-width: 220px;
|
||
}
|
||
.home-note--facts {
|
||
top: calc(50% + 110px);
|
||
max-width: 260px;
|
||
background: #efe6ff;
|
||
box-shadow: inset 1px 1px 0 #f7f1ff, inset -1px -1px 0 #9c93c4;
|
||
}
|
||
.home-note__title {
|
||
font-weight: 700;
|
||
margin-bottom: 4px;
|
||
}
|
||
.home-notes-layer {
|
||
position: fixed;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 1085;
|
||
}
|
||
.home-user-note {
|
||
position: fixed;
|
||
width: 240px;
|
||
min-height: 170px;
|
||
border: 1px solid #6e9a6e;
|
||
background: #CCFFCC;
|
||
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.3);
|
||
pointer-events: auto;
|
||
overflow: hidden;
|
||
}
|
||
.home-user-note__head {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
height: 24px;
|
||
padding: 2px 4px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
||
cursor: move;
|
||
user-select: none;
|
||
}
|
||
.home-user-note__head-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
gap: 4px;
|
||
}
|
||
.home-user-note__add {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: #112211;
|
||
font-size: 22px;
|
||
line-height: 14px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
.home-user-note.is-pinned .home-user-note__head {
|
||
cursor: default;
|
||
}
|
||
.home-user-note__add:disabled {
|
||
opacity: 0.5;
|
||
cursor: default;
|
||
}
|
||
.home-user-note__pin {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 0;
|
||
background: transparent;
|
||
font-size: 13px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
margin-left: auto;
|
||
}
|
||
.home-user-note__pin.is-active {
|
||
filter: saturate(1.3);
|
||
}
|
||
.home-user-note__text {
|
||
width: 100%;
|
||
min-height: 126px;
|
||
border: 0;
|
||
background: transparent;
|
||
resize: both;
|
||
padding: 8px;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.85rem;
|
||
color: #102010;
|
||
}
|
||
.home-user-note__text:focus {
|
||
outline: none;
|
||
}
|
||
.home-user-note__delete {
|
||
position: absolute;
|
||
left: 6px;
|
||
bottom: 4px;
|
||
width: 24px;
|
||
height: 24px;
|
||
border: 0;
|
||
background: transparent;
|
||
color: #0b190b;
|
||
font-size: 34px;
|
||
line-height: 20px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
.home-user-note__delete[disabled] {
|
||
opacity: 0.35;
|
||
cursor: default;
|
||
}
|
||
.home-barcode-btn {
|
||
border: 0;
|
||
background: transparent;
|
||
padding: 0;
|
||
line-height: 0;
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.home-barcode-btn img {
|
||
width: 36px;
|
||
height: auto;
|
||
display: block;
|
||
}
|
||
#homePcComponentsBtn img {
|
||
width: 54px;
|
||
}
|
||
.home-barcode-btn__label {
|
||
background: #ffffff;
|
||
color: #000;
|
||
padding: 2px 6px;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.7rem;
|
||
line-height: 1.1;
|
||
border: 0;
|
||
}
|
||
.home-barcode-btn:active img,
|
||
.home-barcode-btn.is-active img {
|
||
filter: brightness(0.7);
|
||
}
|
||
.home-barcode-btn:active .home-barcode-btn__label,
|
||
.home-barcode-btn.is-active .home-barcode-btn__label {
|
||
background: #000000;
|
||
color: #ffffff;
|
||
}
|
||
.home-modal {
|
||
position: fixed;
|
||
top: 44px;
|
||
left: 12px;
|
||
width: 807px;
|
||
max-width: calc(100vw - 24px);
|
||
background: #dcdcdc;
|
||
border: 1px solid #000;
|
||
box-shadow: 1px 1px 0 #000, 2px 2px 0 #777;
|
||
z-index: 1200;
|
||
}
|
||
.home-modal.hidden {
|
||
display: none;
|
||
}
|
||
.home-modal__titlebar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 2px 4px;
|
||
background: #c0c0c0;
|
||
border-bottom: 1px solid #000;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.85rem;
|
||
line-height: 1;
|
||
cursor: move;
|
||
user-select: none;
|
||
}
|
||
.home-modal__title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-weight: 600;
|
||
letter-spacing: 0.2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.home-modal__spacer {
|
||
flex: 1;
|
||
}
|
||
.home-modal__btn {
|
||
width: 12px;
|
||
height: 12px;
|
||
border: 1px solid #000;
|
||
background: #e6e6e6;
|
||
display: inline-block;
|
||
}
|
||
.home-modal__close {
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.home-modal__close::before,
|
||
.home-modal__close::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 9px;
|
||
height: 2px;
|
||
background: #000;
|
||
transform-origin: center;
|
||
}
|
||
.home-modal__close:active {
|
||
background: #bcbcbc;
|
||
box-shadow: inset 1px 1px 0 #9a9a9a, inset -1px -1px 0 #efefef;
|
||
}
|
||
.home-modal__close:active::before,
|
||
.home-modal__close:active::after {
|
||
background: #111;
|
||
}
|
||
.home-modal__close::before {
|
||
transform: translate(-50%, -50%) rotate(45deg);
|
||
}
|
||
.home-modal__close::after {
|
||
transform: translate(-50%, -50%) rotate(-45deg);
|
||
}
|
||
.home-modal__content {
|
||
background: #efefef;
|
||
border-top: 1px solid #7f7f7f;
|
||
padding: 8px;
|
||
height: 486px;
|
||
overflow: auto;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.82rem;
|
||
}
|
||
.home-modal__row {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px 6px;
|
||
}
|
||
.home-modal__icon {
|
||
display: grid;
|
||
justify-items: center;
|
||
gap: 4px;
|
||
color: #111;
|
||
}
|
||
.home-modal__icon-box {
|
||
width: 34px;
|
||
height: 26px;
|
||
background: #d8d0f0;
|
||
border: 1px solid #000;
|
||
box-shadow: inset 1px 1px 0 #f7f3ff, inset -1px -1px 0 #9a94b3;
|
||
}
|
||
.home-modal__footer {
|
||
border-top: 1px solid #7f7f7f;
|
||
padding: 4px 6px;
|
||
background: #dcdcdc;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.78rem;
|
||
text-align: center;
|
||
}
|
||
.home-modal__embed {
|
||
width: 100%;
|
||
height: 100%;
|
||
min-height: 420px;
|
||
border: 1px solid #7f7f7f;
|
||
background: #fff;
|
||
}
|
||
.home-modal__section-title {
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.88rem;
|
||
margin-bottom: 6px;
|
||
}
|
||
.home-modal__form {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
.home-modal__field label {
|
||
display: block;
|
||
font-size: 0.78rem;
|
||
margin-bottom: 4px;
|
||
}
|
||
.home-modal__input,
|
||
.home-modal__select {
|
||
width: 100%;
|
||
border: 1px solid #6f6f6f;
|
||
padding: 4px 6px;
|
||
background: #fff;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.85rem;
|
||
}
|
||
.home-modal__row-2 {
|
||
display: grid;
|
||
grid-template-columns: 1fr 120px;
|
||
gap: 8px;
|
||
}
|
||
.home-modal__actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
}
|
||
.home-modal__btn-primary {
|
||
border: 1px solid #000;
|
||
background: #d9d9d9;
|
||
padding: 4px 10px;
|
||
font-family: 'Chicago Regular', sans-serif;
|
||
font-size: 0.85rem;
|
||
}
|
||
.home-modal__status {
|
||
font-size: 0.78rem;
|
||
}
|
||
#homePcComponentsModal .home-modal__content {
|
||
display: grid;
|
||
gap: 10px;
|
||
align-content: start;
|
||
padding-top: 4px;
|
||
}
|
||
#homePcComponentsModal .home-modal__input,
|
||
#homePcComponentsModal .home-modal__select {
|
||
height: 26px;
|
||
padding: 4px 6px;
|
||
box-sizing: border-box;
|
||
}
|
||
#homePcComponentsModal .home-modal__section-title {
|
||
margin: 0 0 4px;
|
||
}
|
||
#homePcComponentsModal .home-modal__status {
|
||
margin: 0;
|
||
}
|
||
.pc-components-assign-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 120px auto;
|
||
gap: 10px;
|
||
align-items: end;
|
||
}
|
||
.pc-components-add-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr)) 120px auto;
|
||
gap: 10px;
|
||
align-items: end;
|
||
}
|
||
.pc-components-assign-grid .home-modal__field,
|
||
.pc-components-add-grid .home-modal__field {
|
||
min-width: 0;
|
||
}
|
||
.pc-components-assign-grid .home-modal__select,
|
||
.pc-components-add-grid .home-modal__select {
|
||
width: 100%;
|
||
min-width: 0;
|
||
justify-self: stretch;
|
||
}
|
||
#pcComponentsAddForm .pc-components-add-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-self: end;
|
||
margin-top: 0;
|
||
}
|
||
@media (max-width: 980px) {
|
||
.pc-components-assign-grid {
|
||
grid-template-columns: 1fr 120px;
|
||
}
|
||
.pc-components-assign-grid .pc-components-action {
|
||
grid-column: 1 / -1;
|
||
justify-self: end;
|
||
}
|
||
.pc-components-add-grid {
|
||
grid-template-columns: 1fr 1fr;
|
||
}
|
||
#pcComponentsAddForm .pc-components-add-actions {
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
.home-modal__specs-list {
|
||
margin: 4px 0 0;
|
||
padding-left: 18px;
|
||
font-size: 0.78rem;
|
||
list-style: disc;
|
||
list-style-position: outside;
|
||
}
|
||
.home-modal__specs-list .home-modal__status {
|
||
display: list-item;
|
||
}
|
||
.home-modal__list {
|
||
margin: 4px 0 0;
|
||
padding-left: 16px;
|
||
font-size: 0.78rem;
|
||
}
|
||
@keyframes revealFadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
@keyframes revealFadeOut {
|
||
from { opacity: 1; }
|
||
to { opacity: 0; }
|
||
}
|
||
@keyframes scanlinesOn {
|
||
0% { opacity: 0; }
|
||
35% { opacity: 0.7; }
|
||
100% { opacity: 0.15; }
|
||
}
|
||
@keyframes vignetteIn {
|
||
0% { opacity: 0; }
|
||
100% { opacity: 1; }
|
||
}
|
||
@keyframes glintSweep {
|
||
0% { transform: translateY(18%) translateX(-35%) rotate(-8deg); opacity: 0; }
|
||
40% { opacity: 1; }
|
||
100% { transform: translateY(-8%) translateX(10%) rotate(-8deg); opacity: 0; }
|
||
}
|
||
@keyframes beamPulse {
|
||
0% { opacity: 0; transform: translate(-50%, -50%) scaleX(0.4); }
|
||
45% { opacity: 1; transform: translate(-50%, -50%) scaleX(1); }
|
||
100% { opacity: 0; transform: translate(-50%, -50%) scaleX(1.2); }
|
||
}
|
||
@keyframes contentRise {
|
||
from { opacity: 0; transform: translateY(18px) scale(0.99); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.login-reveal { display: none; }
|
||
.home-wrap .reveal-item { animation: none; opacity: 1; transform: none; }
|
||
}
|
||
.users-modal-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(220px, 260px) 1fr;
|
||
gap: 1rem;
|
||
align-items: start;
|
||
}
|
||
.users-role-note {
|
||
border: 1px solid #000;
|
||
background: #f4f4f4;
|
||
padding: 0.6rem;
|
||
font-size: 0.85rem;
|
||
line-height: 1.35;
|
||
}
|
||
.users-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0 0 0.75rem 0;
|
||
}
|
||
.users-list li {
|
||
border: 1px solid #c7c7c7;
|
||
background: #fff;
|
||
padding: 0.5rem 0.6rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.users-add-form {
|
||
border: 1px solid #c7c7c7;
|
||
padding: 0.75rem;
|
||
background: #fff;
|
||
}
|
||
</style>
|
||
|
||
<div class="home-top-action">
|
||
<div class="home-top-action__stack">
|
||
{% set role = session.get('role') %}
|
||
{% if role != 'viewer' %}
|
||
<button class="home-barcode-btn" id="homeBarcodeBtn" type="button" aria-label="Штрихкод">
|
||
<img src="{{ url_for('static', filename='Desktop_barcod.png') }}" alt="Штрихкод">
|
||
<span class="home-barcode-btn__label">Выдача картриджа</span>
|
||
</button>
|
||
<button class="home-barcode-btn" id="homePhotoBtn" type="button" aria-label="Расходные материалы">
|
||
<img src="{{ url_for('static', filename='Desktop_photoconductor.png') }}" alt="Фотобарабан">
|
||
<span class="home-barcode-btn__label">Расходные материалы</span>
|
||
</button>
|
||
{% endif %}
|
||
<button class="home-barcode-btn" id="homeSearchInvBtn" type="button" aria-label="Поиск оборудования">
|
||
<img src="{{ url_for('static', filename='Desktop_search.png') }}" alt="Поиск по инвентарному номеру">
|
||
<span class="home-barcode-btn__label">Поиск оборудования</span>
|
||
</button>
|
||
{% if role == 'admin' %}
|
||
<button class="home-barcode-btn" id="homeUsersBtn" type="button" aria-label="Пользователи">
|
||
<img src="{{ url_for('static', filename='Desktop_users.png') }}" alt="Пользователи">
|
||
<span class="home-barcode-btn__label">Пользователи</span>
|
||
</button>
|
||
{% endif %}
|
||
{% if role != 'viewer' %}
|
||
<button class="home-barcode-btn" id="homeAddDeviceBtn" type="button" aria-label="Добавить устройство">
|
||
<img src="{{ url_for('static', filename='Desktop_add.png') }}" alt="Добавить устройство">
|
||
<span class="home-barcode-btn__label">Добавить устройство</span>
|
||
</button>
|
||
{% endif %}
|
||
<button class="home-barcode-btn" id="homePcComponentsBtn" type="button" aria-label="Компьютерные комплектующие">
|
||
<img src="{{ url_for('static', filename='Desktop_PCcomponents.png') }}" alt="Компьютерные комплектующие">
|
||
<span class="home-barcode-btn__label">Компьютерные<br>комплектующие</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-note">Welcome to IT-Inventory</div>
|
||
<div class="home-note home-note--facts">
|
||
<div class="home-note__title">Интересный факт</div>
|
||
<div id="homeFactText">Загрузка факта...</div>
|
||
</div>
|
||
<div class="home-notes-layer" id="homeNotesLayer" aria-live="polite"></div>
|
||
|
||
<div class="home-modal hidden" id="homeDesktopModal" role="dialog" aria-label="Выдача картриджей">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Выдача картриджей</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeDesktopClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Выдача по штрихкоду</div>
|
||
<form method="post" action="/issue" class="home-modal__form" id="modalIssueForm">
|
||
<div class="home-modal__field">
|
||
<label for="modalBarcode">Сканируйте штрихкод или введите вручную</label>
|
||
<input id="modalBarcode" name="barcode" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status" id="modalCartridgeName"></div>
|
||
<div class="home-modal__status" id="modalCartridgeStock"></div>
|
||
<div class="home-modal__field d-none" id="modalAddNotice">Картридж не найден. Добавить?</div>
|
||
<div class="home-modal__row-2 d-none" id="modalAddFields">
|
||
<div class="home-modal__field">
|
||
<label for="modalAddModel">Модель</label>
|
||
<input id="modalAddModel" name="model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="modalAddQty">Количество</label>
|
||
<input id="modalAddQty" name="quantity" class="home-modal__input" value="1">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__actions d-none" id="modalAddActions">
|
||
<button class="home-modal__btn-primary" formaction="/add">Добавить</button>
|
||
</div>
|
||
<div class="home-modal__row-2">
|
||
<div class="home-modal__field">
|
||
<label for="modalCabinet">Кабинет</label>
|
||
<select id="modalCabinet" name="cabinet" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for c in cabinets %}
|
||
<option value="{{ c }}">{{ c }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="modalQuantity">Количество</label>
|
||
<input id="modalQuantity" name="quantity" class="home-modal__input" value="1">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary" id="modalIssueSubmit">Выдать</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homePhotoModal" role="dialog" aria-label="Расходные материалы">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Расходные материалы</div>
|
||
<span class="home-modal__btn home-modal__close" id="homePhotoClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Расходный материал</div>
|
||
<form method="post" action="/consumables/issue" class="home-modal__form" id="photoIssueForm">
|
||
<div class="home-modal__field">
|
||
<label for="photoBarcode">Сканируйте штрихкод</label>
|
||
<input id="photoBarcode" name="barcode" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="photoModelText"></div>
|
||
<div class="home-modal__status d-none" id="photoStockText"></div>
|
||
<div class="home-modal__field d-none" id="photoDeviceWrap">
|
||
<label for="photoDevice">Принтер/МФУ</label>
|
||
<select id="photoDevice" name="device_id" class="home-modal__select">
|
||
<option value="">Выберите устройство...</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Выдать</button>
|
||
</div>
|
||
</form>
|
||
<div class="home-modal__section-title d-none" id="photoAddTitle">Добавить расходный материал</div>
|
||
<form method="post" action="/consumables/add" class="home-modal__form d-none" id="photoAddForm">
|
||
<div class="home-modal__field">
|
||
<label for="photoAddBarcode">Штрихкод</label>
|
||
<input id="photoAddBarcode" name="barcode" class="home-modal__input" readonly>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="photoAddModel">Модель</label>
|
||
<input id="photoAddModel" name="model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="photoAddTypeSelect">Тип</label>
|
||
<select name="type" id="photoAddTypeSelect" class="home-modal__select">
|
||
<option value="">Тип расходника...</option>
|
||
{% for t in consumable_types %}
|
||
<option value="{{ t }}">{{ t }}</option>
|
||
{% endfor %}
|
||
<option value="__custom__">Свой тип...</option>
|
||
</select>
|
||
<input name="type_custom" id="photoAddTypeCustom" class="home-modal__input d-none" placeholder="Свой тип">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="photoAddQty">Количество</label>
|
||
<input id="photoAddQty" name="quantity" class="home-modal__input" value="1">
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homeSearchInvModal" role="dialog" aria-label="Поиск оборудования">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Поиск оборудования</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeSearchInvClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Поиск оборудования</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<label for="searchCabinetViewSelect">Выберите кабинет</label>
|
||
<select id="searchCabinetViewSelect" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__status d-none" id="cabinetInfoTitle"></div>
|
||
<div class="home-modal__field d-none" id="cabinetDevicesWrap">
|
||
<label>Устройства (МФУ/Принтеры)</label>
|
||
<ul class="home-modal__list" id="cabinetDevicesList"></ul>
|
||
</div>
|
||
<div class="home-modal__field d-none" id="cabinetComputersWrap">
|
||
<label>Компьютеры/Ноутбуки</label>
|
||
<ul class="home-modal__list" id="cabinetComputersList"></ul>
|
||
</div>
|
||
<div class="home-modal__field d-none" id="cabinetProjectorsWrap">
|
||
<label>Презентационное оборудование</label>
|
||
<ul class="home-modal__list" id="cabinetProjectorsList"></ul>
|
||
</div>
|
||
<div class="home-modal__field d-none" id="cabinetDocsWrap">
|
||
<label>Документ-камеры</label>
|
||
<ul class="home-modal__list" id="cabinetDocsList"></ul>
|
||
</div>
|
||
<div class="home-modal__status d-none" id="cabinetEmptyNote">В этом кабинете нет оборудования.</div>
|
||
<div class="home-modal__section-title mt-2">Поиск по инвентарному номеру</div>
|
||
<div class="home-modal__field">
|
||
<label for="searchInvInput">Введите инвентарный номер</label>
|
||
<input id="searchInvInput" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="searchInfoInv"></div>
|
||
<div class="home-modal__status d-none" id="searchInfoType"></div>
|
||
<div class="home-modal__status d-none" id="searchInfoBrand"></div>
|
||
<div class="home-modal__status d-none" id="searchInfoSerial"></div>
|
||
<div class="home-modal__status d-none" id="searchInfoDate"></div>
|
||
<div class="home-modal__status d-none" id="searchInfoCabinet"></div>
|
||
<div class="home-modal__field d-none" id="searchConsumablesWrap">
|
||
<label>Привязанные материалы</label>
|
||
<ul class="home-modal__list" id="searchConsumablesList"></ul>
|
||
</div>
|
||
<div class="home-modal__field d-none" id="searchSpecsWrap">
|
||
<label>Характеристики</label>
|
||
<ul class="home-modal__specs-list">
|
||
<li class="home-modal__status" id="searchSpecCpu"></li>
|
||
<li class="home-modal__status" id="searchSpecGpu"></li>
|
||
<li class="home-modal__status" id="searchSpecRam"></li>
|
||
<li class="home-modal__status" id="searchSpecStorage"></li>
|
||
<li class="home-modal__status" id="searchSpecOs"></li>
|
||
<li class="home-modal__status d-none" id="searchSpecMotherboard"></li>
|
||
</ul>
|
||
</div>
|
||
<div class="home-modal__field d-none" id="searchKitWrap">
|
||
<label>Состав комплекта</label>
|
||
<div class="home-modal__status" id="searchKitProjectorTitle"></div>
|
||
<ul class="home-modal__specs-list" id="searchKitProjectorList"></ul>
|
||
<div class="home-modal__status" id="searchKitBoardTitle"></div>
|
||
<ul class="home-modal__specs-list" id="searchKitBoardList"></ul>
|
||
<div class="home-modal__status" id="searchKitComputerTitle"></div>
|
||
<ul class="home-modal__specs-list" id="searchKitComputerList"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homePrintersModal" role="dialog" aria-label="МФУ/Принтеры">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">МФУ/Принтеры</div>
|
||
<span class="home-modal__btn home-modal__close" id="homePrintersClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">МФУ/Принтеры</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<label for="printersInv">Введите инвентарный номер</label>
|
||
<input id="printersInv" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="printersInfoInv"></div>
|
||
<div class="home-modal__status d-none" id="printersInfoBrand"></div>
|
||
<div class="home-modal__status d-none" id="printersInfoSerial"></div>
|
||
<div class="home-modal__status d-none" id="printersInfoDate"></div>
|
||
<div class="home-modal__status d-none" id="printersInfoCabinet"></div>
|
||
<form method="post" class="home-modal__form d-none" id="printersCabinetForm">
|
||
<input type="hidden" name="id" id="printersId">
|
||
<div class="home-modal__field">
|
||
<label for="printersCabinetSelect">Выбрать кабинет</label>
|
||
<select id="printersCabinetSelect" name="cabinet_id" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Сменить расположение</button>
|
||
</div>
|
||
</form>
|
||
<div class="home-modal__field d-none" id="printersConsumablesWrap">
|
||
<label>Привязанные материалы</label>
|
||
<ul class="home-modal__list" id="printersConsumablesList"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homeComputersModal" role="dialog" aria-label="Компьютеры/Ноутбуки">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Компьютеры/Ноутбуки</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeComputersClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Компьютеры/Ноутбуки</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<label for="computersInv">Введите инвентарный номер</label>
|
||
<input id="computersInv" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="computersInfoInv"></div>
|
||
<div class="home-modal__status d-none" id="computersInfoBrand"></div>
|
||
<div class="home-modal__status d-none" id="computersInfoSerial"></div>
|
||
<div class="home-modal__status d-none" id="computersInfoDate"></div>
|
||
<div class="home-modal__status d-none" id="computersInfoCabinet"></div>
|
||
<form method="post" class="home-modal__form d-none" id="computersCabinetForm">
|
||
<input type="hidden" name="id" id="computersId">
|
||
<div class="home-modal__field">
|
||
<label for="computersCabinetSelect">Выбрать кабинет</label>
|
||
<select id="computersCabinetSelect" name="cabinet_id" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Сменить расположение</button>
|
||
</div>
|
||
</form>
|
||
<div class="home-modal__field d-none" id="computersSpecsWrap">
|
||
<label>Характеристики</label>
|
||
<ul class="home-modal__specs-list">
|
||
<li class="home-modal__status" id="computersSpecCpu"></li>
|
||
<li class="home-modal__status" id="computersSpecGpu"></li>
|
||
<li class="home-modal__status" id="computersSpecRam"></li>
|
||
<li class="home-modal__status" id="computersSpecStorage"></li>
|
||
<li class="home-modal__status" id="computersSpecOs"></li>
|
||
<li class="home-modal__status d-none" id="computersSpecMotherboard"></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homeProjectorsModal" role="dialog" aria-label="Проекторы">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Проекторы</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeProjectorsClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Проекторы</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<label for="projectorsInv">Введите инвентарный номер</label>
|
||
<input id="projectorsInv" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoInv"></div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoType"></div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoBrand"></div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoSerial"></div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoDate"></div>
|
||
<div class="home-modal__status d-none" id="projectorsInfoCabinet"></div>
|
||
<form method="post" class="home-modal__form d-none" id="projectorsCabinetForm">
|
||
<input type="hidden" name="id" id="projectorsId">
|
||
<div class="home-modal__field">
|
||
<label for="projectorsCabinetSelect">Выбрать кабинет</label>
|
||
<select id="projectorsCabinetSelect" name="cabinet_id" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Сменить расположение</button>
|
||
</div>
|
||
</form>
|
||
<div class="home-modal__field d-none" id="projectorsKitWrap">
|
||
<label>Состав комплекта</label>
|
||
<div class="home-modal__status" id="kitProjectorTitle"></div>
|
||
<ul class="home-modal__specs-list" id="kitProjectorList"></ul>
|
||
<div class="home-modal__status" id="kitBoardTitle"></div>
|
||
<ul class="home-modal__specs-list" id="kitBoardList"></ul>
|
||
<div class="home-modal__status" id="kitComputerTitle"></div>
|
||
<ul class="home-modal__specs-list" id="kitComputerList"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homeDocCameraModal" role="dialog" aria-label="Документ-камеры">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Документ-камеры</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeDocCameraClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Документ-камеры</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<label for="docCameraInv">Введите инвентарный номер</label>
|
||
<input id="docCameraInv" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__status d-none" id="docCameraInfoInv"></div>
|
||
<div class="home-modal__status d-none" id="docCameraInfoBrand"></div>
|
||
<div class="home-modal__status d-none" id="docCameraInfoSerial"></div>
|
||
<div class="home-modal__status d-none" id="docCameraInfoDate"></div>
|
||
<div class="home-modal__status d-none" id="docCameraInfoCabinet"></div>
|
||
<form method="post" class="home-modal__form d-none" id="docCameraCabinetForm">
|
||
<input type="hidden" name="id" id="docCameraId">
|
||
<div class="home-modal__field">
|
||
<label for="docCameraCabinetSelect">Выбрать кабинет</label>
|
||
<select id="docCameraCabinetSelect" name="cabinet_id" class="home-modal__select">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Сменить расположение</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homeAddDeviceModal" role="dialog" aria-label="Добавить устройство">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Добавить устройство</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeAddDeviceClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Добавить устройство</div>
|
||
<div class="home-modal__stack">
|
||
<div class="home-modal__field">
|
||
<label for="addDeviceType">Выберите тип</label>
|
||
<select id="addDeviceType" class="home-modal__select">
|
||
<option value="">Выберите тип...</option>
|
||
<option value="device">МФУ/Принтер</option>
|
||
<option value="computer">Персональный компьютер/Ноутбук</option>
|
||
<option value="projector">Проектор</option>
|
||
<option value="document_camera">Документ-камера</option>
|
||
</select>
|
||
</div>
|
||
|
||
<form method="post" action="/devices/add" class="home-modal__form d-none" data-add-type="device">
|
||
<div class="home-modal__field">
|
||
<label>Введите инвентарный номер</label>
|
||
<input name="inventory_number" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Бренд</label>
|
||
<select name="brand_select" class="home-modal__select" id="homeDeviceBrandSelect">
|
||
<option value="">Бренд...</option>
|
||
{% for b in device_brands %}
|
||
<option value="{{ b }}">{{ b }}</option>
|
||
{% endfor %}
|
||
<option value="__custom__">+ Добавить бренд</option>
|
||
</select>
|
||
<input name="brand_custom" id="homeDeviceBrandCustom" class="home-modal__input mt-2 d-none" placeholder="Новый бренд">
|
||
<input type="hidden" name="brand" id="homeDeviceBrandValue">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель</label>
|
||
<input name="model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Серийный №</label>
|
||
<input name="serial_number" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field">
|
||
<label>Тип</label>
|
||
<select name="type" class="home-modal__select">
|
||
<option value="">Тип...</option>
|
||
<option value="mfp">МФУ</option>
|
||
<option value="printer">Принтер</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Расположение</label>
|
||
<select name="cabinet_id" class="home-modal__select">
|
||
<option value="">Кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Дата добавления</label>
|
||
<input name="date_in_operation" type="date" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field" style="grid-column: 1 / span 2;">
|
||
<label>Примечание</label>
|
||
<input name="note" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__actions" style="align-self: end;">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/computers/add" class="home-modal__form d-none" data-add-type="computer">
|
||
<div class="home-modal__field">
|
||
<label>Введите инвентарный номер</label>
|
||
<input name="inventory_number" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Бренд</label>
|
||
<select name="brand_select" class="home-modal__select" id="homeComputerBrandSelect">
|
||
<option value="">Бренд...</option>
|
||
{% for b in computer_brands %}
|
||
<option value="{{ b }}">{{ b }}</option>
|
||
{% endfor %}
|
||
<option value="__custom__">+ Добавить бренд</option>
|
||
</select>
|
||
<input name="brand_custom" id="homeComputerBrandCustom" class="home-modal__input mt-2 d-none" placeholder="Новый бренд">
|
||
<input type="hidden" name="brand" id="homeComputerBrandValue">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель</label>
|
||
<input name="model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Серийный №</label>
|
||
<input name="serial_number" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field">
|
||
<label>Тип</label>
|
||
<select name="type" class="home-modal__select">
|
||
<option value="">Тип...</option>
|
||
<option value="pc">Персональный компьютер</option>
|
||
<option value="laptop">Ноутбук</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Расположение</label>
|
||
<select name="cabinet_id" class="home-modal__select">
|
||
<option value="">Кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Дата добавления</label>
|
||
<input name="date_in_operation" type="date" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field" style="grid-column: 1 / span 2;">
|
||
<label>Примечание</label>
|
||
<input name="note" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__actions" style="align-self: end;">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary" type="button" id="homeComputerSpecsToggle">Характеристики</button>
|
||
</div>
|
||
<div class="home-modal__form d-none" id="homeComputerSpecs">
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Бренд CPU</label>
|
||
<select name="cpu_brand" class="home-modal__select">
|
||
<option value="">Бренд CPU...</option>
|
||
<option value="Intel">Intel</option>
|
||
<option value="AMD">AMD</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель CPU</label>
|
||
<input name="cpu_model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель GPU</label>
|
||
<input name="gpu_model" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Объём оперативной памяти</label>
|
||
<input name="memory_size" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Тип памяти</label>
|
||
<select name="memory_type" class="home-modal__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="home-modal__field">
|
||
<label>Объём накопителя</label>
|
||
<input name="storage_size" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field">
|
||
<label>Операционная система</label>
|
||
<select name="os" class="home-modal__select">
|
||
<option value="">ОС...</option>
|
||
<option value="Windows 7 (x86)">Windows 7 (x86)</option>
|
||
<option value="Windows 7 (x64)">Windows 7 (x64)</option>
|
||
<option value="Windows 8 (x86)">Windows 8 (x86)</option>
|
||
<option value="Windows 8 (x64)">Windows 8 (x64)</option>
|
||
<option value="Windows 8.1 (x86)">Windows 8.1 (x86)</option>
|
||
<option value="Windows 8.1 (x64)">Windows 8.1 (x64)</option>
|
||
<option value="Windows 10 (x86)">Windows 10 (x86)</option>
|
||
<option value="Windows 10 (x64)">Windows 10 (x64)</option>
|
||
<option value="Windows 11">Windows 11</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field" style="grid-column: 2 / span 1;">
|
||
<label>Примечание</label>
|
||
<input name="note" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__actions" style="align-self: end;">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/projectors/add" class="home-modal__form d-none" data-add-type="projector">
|
||
<div class="home-modal__field">
|
||
<label>Тип</label>
|
||
<select name="kit_type" class="home-modal__select" id="homeProjectorKitType">
|
||
<option value="">Тип...</option>
|
||
<option value="kit">Комплект</option>
|
||
<option value="board">Доска</option>
|
||
<option value="projector">Проектор</option>
|
||
<option value="display">Интерактивный экран</option>
|
||
<option value="tv">Телевизор</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="home-modal__row-2" data-kit-types="kit,projector,board,display,tv">
|
||
<div class="home-modal__field">
|
||
<label>Введите инвентарный номер</label>
|
||
<input name="inventory_number" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-2" data-kit-types="kit" id="homeProjectorBrandRowKit"></div>
|
||
|
||
<div class="home-modal__row-3" data-kit-types="projector,board,display,tv" id="homeProjectorBrandRowMain">
|
||
<div class="home-modal__field" id="homeProjectorBrandField">
|
||
<label>Бренд</label>
|
||
<select name="brand_select" class="home-modal__select" id="homeProjectorBrandSelect">
|
||
<option value="">Бренд...</option>
|
||
{% for b in projector_brands %}
|
||
<option value="{{ b }}">{{ b }}</option>
|
||
{% endfor %}
|
||
<option value="__custom__">+ Добавить бренд</option>
|
||
</select>
|
||
<input name="brand_custom" id="homeProjectorBrandCustom" class="home-modal__input mt-2 d-none" placeholder="Новый бренд">
|
||
<input type="hidden" name="brand" id="homeProjectorBrandValue">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label id="homeProjectorModelLabel">Модель проектора</label>
|
||
<input name="projector_model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label id="homeProjectorSerialLabel">Серийный № проектора</label>
|
||
<input name="projector_serial" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-3" data-kit-types="kit">
|
||
<div class="home-modal__field">
|
||
<label>Инв. № проектора</label>
|
||
<input name="projector_inventory_number" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель проектора</label>
|
||
<input name="projector_model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Серийный № проектора</label>
|
||
<input name="projector_serial" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-3" data-kit-types="kit">
|
||
<div class="home-modal__field">
|
||
<label>Инв. № доски</label>
|
||
<input name="board_inventory_number" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель доски</label>
|
||
<input name="board_model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Серийный № доски</label>
|
||
<input name="board_serial" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-3" data-kit-types="kit,display">
|
||
<div class="home-modal__field">
|
||
<label>Ноутбук / ПК (модель)</label>
|
||
<select name="computer_id" class="home-modal__select" id="homeProjectorComputerSelect">
|
||
<option value="">Ноутбук / ПК (модель)...</option>
|
||
{% for cid, cinv, cbrand, cmodel, ctype in computers %}
|
||
{% set comp_text = [cbrand, cmodel]|select|join(' ') %}
|
||
<option value="{{ cid }}" data-inv="{{ cinv }}" data-label="{{ comp_text }}">{{ comp_text }}{% if cinv %} ({{ cinv }}){% endif %}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Инв. № ноутбука/ПК</label>
|
||
<input name="computer_inventory_number" class="home-modal__input" id="homeProjectorComputerInv">
|
||
</div>
|
||
<div class="home-modal__field"></div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-3-wide" data-kit-types="kit,projector,board,display,tv">
|
||
<div class="home-modal__field">
|
||
<label>Расположение</label>
|
||
<select name="cabinet_id" class="home-modal__select">
|
||
<option value="">Кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Дата добавления</label>
|
||
<input name="date_in_operation" type="date" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field"></div>
|
||
</div>
|
||
|
||
<div class="home-modal__row-3-wide" data-kit-types="kit,projector,board,display,tv">
|
||
<div class="home-modal__field" style="grid-column: 1 / span 2;">
|
||
<label>Примечание</label>
|
||
<input name="note" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__actions" style="align-self: end;">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/document-cameras/add" class="home-modal__form d-none" data-add-type="document_camera">
|
||
<div class="home-modal__field">
|
||
<label>Введите инвентарный номер</label>
|
||
<input name="inventory_number" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Бренд</label>
|
||
<select name="brand_select" class="home-modal__select" id="homeDocBrandSelect">
|
||
<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="homeDocBrandCustom" class="home-modal__input mt-2 d-none" placeholder="Новый бренд">
|
||
<input type="hidden" name="brand" id="homeDocBrandValue">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Модель</label>
|
||
<input name="model" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Серийный №</label>
|
||
<input name="serial_number" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3-wide">
|
||
<div class="home-modal__field">
|
||
<label>Расположение</label>
|
||
<select name="cabinet_id" class="home-modal__select">
|
||
<option value="">Кабинет...</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Дата добавления</label>
|
||
<input name="date_in_operation" type="date" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__actions" style="align-self: end;">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
<div class="home-modal hidden" id="homePcComponentsModal" role="dialog" aria-label="Компьютерные комплектующие">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Компьютерные комплектующие</div>
|
||
<span class="home-modal__btn home-modal__close" id="homePcComponentsClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="home-modal__section-title">Сканируйте или введите модель/штрихкод</div>
|
||
<div class="home-modal__form">
|
||
<div class="home-modal__field">
|
||
<input id="pcComponentsLookupInput" class="home-modal__input" autocomplete="off" placeholder="Модель или штрихкод">
|
||
</div>
|
||
<div class="home-modal__status" id="pcComponentsLookupStatus"></div>
|
||
</div>
|
||
|
||
<form method="post" action="/components/assign" class="home-modal__form d-none" id="pcComponentsAssignForm" style="margin-top: 10px;">
|
||
<input type="hidden" name="component_id" id="pcComponentsAssignId">
|
||
<div class="home-modal__status"><strong>Модель:</strong> <span id="pcComponentsFoundModel">-</span></div>
|
||
<div class="home-modal__status"><strong>Тип:</strong> <span id="pcComponentsFoundType">-</span></div>
|
||
<div class="home-modal__status"><strong>Остаток:</strong> <span id="pcComponentsFoundQty">0</span></div>
|
||
<div class="pc-components-assign-grid">
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsComputerSelect">Выберите устройство</label>
|
||
<select id="pcComponentsComputerSelect" name="computer_id" class="home-modal__select">
|
||
<option value="">Выберите устройство...</option>
|
||
{% for cid, inv, brand, model, ctype in computers %}
|
||
<option value="{{ cid }}">{{ inv or '?' }} - {{ (brand ~ ' ' if brand else '') ~ (model or '?') }} ({{ 'Компьютер' if ctype == 'pc' else 'Ноутбук' }})</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsAssignQty">Кол-во</label>
|
||
<input id="pcComponentsAssignQty" name="quantity" class="home-modal__input" value="1">
|
||
</div>
|
||
<div class="home-modal__actions pc-components-action">
|
||
<button class="home-modal__btn-primary">Выбрать</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/components/add_quick" class="home-modal__form d-none" id="pcComponentsAddForm" style="margin-top: 10px;">
|
||
<div class="home-modal__status">Запись не найдена. Добавить?</div>
|
||
<div class="pc-components-add-grid">
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsAddBarcode">Штрихкод (необязательно)</label>
|
||
<input id="pcComponentsAddBarcode" name="barcode" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsAddModel">Модель</label>
|
||
<input id="pcComponentsAddModel" name="model" class="home-modal__input" autocomplete="off">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsAddType">Тип</label>
|
||
<select id="pcComponentsAddType" name="component_type" class="home-modal__select">
|
||
<option value="">Выберите тип...</option>
|
||
{% for t in component_types %}
|
||
<option value="{{ t }}">{{ t }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label for="pcComponentsAddQty">Кол-во</label>
|
||
<input id="pcComponentsAddQty" name="quantity" class="home-modal__input" value="1">
|
||
</div>
|
||
<div class="home-modal__actions pc-components-add-actions pc-components-action">
|
||
<button class="home-modal__btn-primary">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
|
||
|
||
{% if session.get('role') == 'admin' %}
|
||
<div class="home-modal hidden" id="homeUsersModal" role="dialog" aria-label="Пользователи">
|
||
<div class="home-modal__titlebar">
|
||
<span class="home-modal__btn" aria-hidden="true"></span>
|
||
<div class="home-modal__title">Пользователи</div>
|
||
<span class="home-modal__btn home-modal__close" id="homeUsersClose" aria-label="Close"></span>
|
||
</div>
|
||
<div class="home-modal__content">
|
||
<div class="users-modal-grid">
|
||
<div class="users-role-note">
|
||
<div><strong>Администратор</strong> — полный доступ.</div>
|
||
<div class="mt-2"><strong>Завхоз</strong> — ограниченный доступ, но есть доступ к передвижению техникой, добавлению картриджей и расходных материалов, а также добавлению и удалению оборудования.</div>
|
||
<div class="mt-2"><strong>Просмотр</strong> — доступ только для просмотра.</div>
|
||
</div>
|
||
<div>
|
||
<ul class="users-list" id="usersList">
|
||
{% for row in users_rows %}
|
||
<li>
|
||
<div><strong>{{ row.full_name }}</strong></div>
|
||
<div>Роль: {{ row.role }}</div>
|
||
<div>Логин: {{ row.login }}</div>
|
||
<div>Пароль: {{ row.password }}</div>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
<div class="d-flex justify-content-start mb-2">
|
||
<button class="home-modal__btn-primary" type="button" id="usersAddToggle">Добавить пользователя</button>
|
||
</div>
|
||
<form method="post" action="/users/add" class="users-add-form d-none" id="usersAddForm">
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Фамилия</label>
|
||
<input name="last_name" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Имя</label>
|
||
<input name="first_name" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Отчество</label>
|
||
<input name="patronymic" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-3">
|
||
<div class="home-modal__field">
|
||
<label>Роль</label>
|
||
<select name="role" class="home-modal__select">
|
||
<option value="">Выберите роль...</option>
|
||
<option value="admin">Администратор</option>
|
||
<option value="storekeeper">Завхоз</option>
|
||
<option value="viewer">Режим просмотра</option>
|
||
</select>
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Логин</label>
|
||
<input name="login" class="home-modal__input">
|
||
</div>
|
||
<div class="home-modal__field">
|
||
<label>Пароль</label>
|
||
<input name="password" type="password" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__row-2">
|
||
<div class="home-modal__field">
|
||
<label>Подтвердите пароль</label>
|
||
<input name="password_confirm" type="password" class="home-modal__input">
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__actions">
|
||
<button class="home-modal__btn-primary">Сохранить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="home-modal__footer"></div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="d-flex justify-content-center">
|
||
<div class="w-100 home-wrap" style="max-width: 860px;">
|
||
<!-- removed title -->
|
||
|
||
{% if session.get('role') not in ('admin','storekeeper') %}
|
||
<div class="alert alert-secondary">Режим просмотра: выдача доступна только администратору и завхозу.</div>
|
||
{% endif %}
|
||
|
||
|
||
|
||
<div class="card mb-3 d-none" id="addCard">
|
||
<div class="card-body">
|
||
<h5 class="card-title mb-2">Штрихкод не найден — добавить в базу</h5>
|
||
<div class="row g-2 align-items-center mb-2">
|
||
<div class="col-md-4">
|
||
<select id="addKindSelect" class="form-select">
|
||
<option value="">Что добавляем...</option>
|
||
<option value="cartridge">Картридж</option>
|
||
<option value="consumable">Расходный материал</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="/add" class="row g-2 align-items-center d-none" id="addCartridgeForm">
|
||
<div class="col-md-3">
|
||
<input name="barcode" id="addCartridgeBarcode" class="form-control" placeholder="Штрихкод" readonly>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<input name="model" class="form-control" placeholder="Модель">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input name="quantity" class="form-control" value="1">
|
||
</div>
|
||
<div class="col-md-2 d-grid">
|
||
<button class="btn btn-primary" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Добавить</button>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/consumables/add" class="row g-2 align-items-center d-none" id="addConsumableForm">
|
||
<div class="col-md-3">
|
||
<input name="barcode" id="addConsumableBarcode" class="form-control" placeholder="Штрихкод" readonly>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<input name="model" class="form-control" placeholder="Модель">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<select name="type" id="consumableTypeSelect" class="form-select">
|
||
<option value="">Тип расходника...</option>
|
||
{% for t in consumable_types %}
|
||
<option value="{{ t }}">{{ t }}</option>
|
||
{% endfor %}
|
||
<option value="__custom__">Свой тип...</option>
|
||
</select>
|
||
<input name="type_custom" id="consumableTypeCustom" class="form-control mt-2 d-none" placeholder="Свой тип">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input name="quantity" class="form-control" value="1">
|
||
</div>
|
||
<div class="col-md-1 d-grid">
|
||
<button class="btn btn-primary" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3 d-none" id="deviceCard">
|
||
<div class="card-body">
|
||
<h5 class="card-title mb-2" id="deviceCardTitle">Устройство</h5>
|
||
<div class="row g-2">
|
||
<div class="col-md-4">
|
||
<div class="form-text">Инвентарный номер</div>
|
||
<div id="deviceInventory" class="fw-semibold">—</div>
|
||
</div>
|
||
<div class="col-md-4" id="deviceBrandWrap">
|
||
<div class="form-text">Бренд</div>
|
||
<div id="deviceBrand">—</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="form-text">Модель</div>
|
||
<div id="deviceModel">—</div>
|
||
</div>
|
||
<div class="col-md-4" id="deviceSerialWrap">
|
||
<div class="form-text">Серийный номер</div>
|
||
<div id="deviceSerial">—</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="form-text">Кабинет</div>
|
||
<div id="deviceCabinet">—</div>
|
||
</div>
|
||
<div class="col-md-9">
|
||
<div class="form-text">Дата ввода</div>
|
||
<div id="deviceDate">—</div>
|
||
</div>
|
||
</div>
|
||
<div class="d-none" id="deviceConsumablesSection">
|
||
<hr class="my-3">
|
||
<div>
|
||
<div class="form-text">Привязанные расходные материалы</div>
|
||
<ul class="mb-0" id="deviceConsumablesList"></ul>
|
||
</div>
|
||
</div>
|
||
<div class="d-none" id="computerSpecsSection">
|
||
<hr class="my-3">
|
||
<div class="form-text">Характеристики</div>
|
||
<div class="border rounded-3 p-2 small">
|
||
<div class="row g-1">
|
||
<div class="col-12">CPU: <span id="compCpu">—</span></div>
|
||
<div class="col-12">GPU: <span id="compGpu">—</span></div>
|
||
<div class="col-12">RAM: <span id="compMemory">—</span></div>
|
||
<div class="col-12">Накопитель: <span id="compStorage">—</span></div>
|
||
<div class="col-12" id="compMotherboardRow">Материнская плата: <span id="compMotherboard">—</span></div>
|
||
<div class="col-12">ОС: <span id="compOs">—</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="d-none" id="projectorKitSection">
|
||
<hr class="my-3">
|
||
<div class="form-text">Комплект</div>
|
||
<div class="border rounded-3 p-2 small">
|
||
<div class="row g-2">
|
||
<div class="col-md-4">
|
||
<div class="fw-semibold mb-1">Проектор</div>
|
||
<div>Инв. №: <span id="projInv">—</span></div>
|
||
<div>Бренд: <span id="projBrand">—</span></div>
|
||
<div>Серийный №: <span id="projSerial">—</span></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="fw-semibold mb-1">Доска</div>
|
||
<div>Инв. №: <span id="boardInv">—</span></div>
|
||
<div>Бренд: <span id="boardBrand">—</span></div>
|
||
<div>Модель: <span id="boardModel">—</span></div>
|
||
<div>Серийный №: <span id="boardSerial">—</span></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="fw-semibold mb-1">Компьютер/ноутбук</div>
|
||
<div>Инв. №: <span id="kitCompInv">—</span></div>
|
||
<div>Бренд: <span id="kitCompBrand">—</span></div>
|
||
<div>Модель: <span id="kitCompModel">—</span></div>
|
||
<div>Серийный №: <span id="kitCompSerial">—</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-none" id="projectorComputerSection">
|
||
<hr class="my-3">
|
||
<div class="form-text">Компьютер/ноутбук</div>
|
||
<div class="border rounded-3 p-2 small">
|
||
<div class="row g-1">
|
||
<div class="col-12">Модель: <span id="projCompModel">—</span></div>
|
||
<div class="col-12">Инв. №: <span id="projCompInv">—</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-3">
|
||
<form method="post" id="equipmentCabinetForm" class="row g-2 align-items-end">
|
||
<input type="hidden" name="id" id="equipmentId">
|
||
<div class="col-md-4">
|
||
<label class="form-label mb-1">Сменить кабинет</label>
|
||
<select name="cabinet_id" class="form-select" id="equipmentCabinetSelect" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>
|
||
<option value="">—</option>
|
||
{% for cid, cname in cabinets_full %}
|
||
<option value="{{ cid }}">{{ cname }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3 d-grid">
|
||
<button class="btn btn-primary" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Сменить расположение</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3 d-none" id="cartridgeCard">
|
||
<div class="card-body">
|
||
<h5 class="card-title mb-2">Картридж</h5>
|
||
<form method="post" action="/issue" class="row g-2 align-items-center">
|
||
<input type="hidden" name="barcode" id="cartridgeBarcode">
|
||
<div class="col-md-4">
|
||
<input class="form-control" id="cartridgeModel" placeholder="Модель" readonly>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input name="quantity" class="form-control" value="1">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<select name="cabinet" class="form-select" id="cartridgeCabinet">
|
||
<option value="">Выберите кабинет...</option>
|
||
{% for c in cabinets %}
|
||
<option value="{{ c }}">{{ c }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-2 d-grid">
|
||
<button class="btn btn-danger" id="cartridgeIssueSubmit" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Выдать</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3 d-none" id="consumableCard">
|
||
<div class="card-body">
|
||
<h5 class="card-title mb-2">Расходный материал</h5>
|
||
<form method="post" action="/consumables/issue" class="row g-2 align-items-center">
|
||
<input type="hidden" name="barcode" id="consumableBarcode">
|
||
<div class="col-md-4">
|
||
<input class="form-control" id="consumableModel" placeholder="Модель" readonly>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<select name="device_id" class="form-select">
|
||
<option value="">Выберите устройство...</option>
|
||
{% for did, inv, model, dtype in devices %}
|
||
<option value="{{ did }}">{{ inv }} — {{ model }} ({{ 'Принтер' if dtype == 'printer' else 'МФУ' }})</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-2 d-grid">
|
||
<button class="btn btn-danger" {% if session.get('role') not in ('admin','storekeeper') %}disabled{% endif %}>Выдать</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
const input = document.getElementById('barcodeInput');
|
||
const hint = document.getElementById('barcodeHint');
|
||
const cartCard = document.getElementById('cartridgeCard');
|
||
const consCard = document.getElementById('consumableCard');
|
||
const addCard = document.getElementById('addCard');
|
||
const deviceCard = document.getElementById('deviceCard');
|
||
const deviceCardTitle = document.getElementById('deviceCardTitle');
|
||
const deviceInventory = document.getElementById('deviceInventory');
|
||
const deviceBrand = document.getElementById('deviceBrand');
|
||
const deviceBrandWrap = document.getElementById('deviceBrandWrap');
|
||
const deviceSerialWrap = document.getElementById('deviceSerialWrap');
|
||
const deviceSerial = document.getElementById('deviceSerial');
|
||
const deviceModel = document.getElementById('deviceModel');
|
||
const deviceCabinet = document.getElementById('deviceCabinet');
|
||
const deviceDate = document.getElementById('deviceDate');
|
||
const deviceConsumablesList = document.getElementById('deviceConsumablesList');
|
||
const deviceConsumablesSection = document.getElementById('deviceConsumablesSection');
|
||
const computerSpecsSection = document.getElementById('computerSpecsSection');
|
||
const compCpu = document.getElementById('compCpu');
|
||
const compGpu = document.getElementById('compGpu');
|
||
const compMemory = document.getElementById('compMemory');
|
||
const compStorage = document.getElementById('compStorage');
|
||
const compMotherboardRow = document.getElementById('compMotherboardRow');
|
||
const compMotherboard = document.getElementById('compMotherboard');
|
||
const compOs = document.getElementById('compOs');
|
||
const projectorKitSection = document.getElementById('projectorKitSection');
|
||
const projInv = document.getElementById('projInv');
|
||
const projBrand = document.getElementById('projBrand');
|
||
const projSerial = document.getElementById('projSerial');
|
||
const boardModel = document.getElementById('boardModel');
|
||
const boardInv = document.getElementById('boardInv');
|
||
const boardBrand = document.getElementById('boardBrand');
|
||
const boardSerial = document.getElementById('boardSerial');
|
||
const kitCompInv = document.getElementById('kitCompInv');
|
||
const kitCompBrand = document.getElementById('kitCompBrand');
|
||
const kitCompModel = document.getElementById('kitCompModel');
|
||
const kitCompSerial = document.getElementById('kitCompSerial');
|
||
const projectorComputerSection = document.getElementById('projectorComputerSection');
|
||
const projCompModel = document.getElementById('projCompModel');
|
||
const projCompInv = document.getElementById('projCompInv');
|
||
const equipmentCabinetForm = document.getElementById('equipmentCabinetForm');
|
||
const equipmentId = document.getElementById('equipmentId');
|
||
const equipmentCabinetSelect = document.getElementById('equipmentCabinetSelect');
|
||
const addKindSelect = document.getElementById('addKindSelect');
|
||
const addCartridgeForm = document.getElementById('addCartridgeForm');
|
||
const addConsumableForm = document.getElementById('addConsumableForm');
|
||
const addCartridgeBarcode = document.getElementById('addCartridgeBarcode');
|
||
const addConsumableBarcode = document.getElementById('addConsumableBarcode');
|
||
const consumableTypeSelect = document.getElementById('consumableTypeSelect');
|
||
const consumableTypeCustom = document.getElementById('consumableTypeCustom');
|
||
const cartModel = document.getElementById('cartridgeModel');
|
||
const cartBarcode = document.getElementById('cartridgeBarcode');
|
||
const consModel = document.getElementById('consumableModel');
|
||
const consBarcode = document.getElementById('consumableBarcode');
|
||
const desktopBtn = document.getElementById('homeBarcodeBtn');
|
||
const desktopModal = document.getElementById('homeDesktopModal');
|
||
const desktopClose = document.getElementById('homeDesktopClose');
|
||
const desktopTitlebar = desktopModal ? desktopModal.querySelector('.home-modal__titlebar') : null;
|
||
const photoBtn = document.getElementById('homePhotoBtn');
|
||
const photoModal = document.getElementById('homePhotoModal');
|
||
const photoClose = document.getElementById('homePhotoClose');
|
||
const photoTitlebar = photoModal ? photoModal.querySelector('.home-modal__titlebar') : null;
|
||
const searchInvBtn = document.getElementById('homeSearchInvBtn');
|
||
const searchInvModal = document.getElementById('homeSearchInvModal');
|
||
const searchInvClose = document.getElementById('homeSearchInvClose');
|
||
const searchInvTitlebar = searchInvModal ? searchInvModal.querySelector('.home-modal__titlebar') : null;
|
||
const printersBtn = document.getElementById('homePrintersBtn');
|
||
const printersModal = document.getElementById('homePrintersModal');
|
||
const printersClose = document.getElementById('homePrintersClose');
|
||
const printersTitlebar = printersModal ? printersModal.querySelector('.home-modal__titlebar') : null;
|
||
const computersBtn = document.getElementById('homeComputersBtn');
|
||
const computersModal = document.getElementById('homeComputersModal');
|
||
const computersClose = document.getElementById('homeComputersClose');
|
||
const computersTitlebar = computersModal ? computersModal.querySelector('.home-modal__titlebar') : null;
|
||
const projectorsBtn = document.getElementById('homeProjectBtn');
|
||
const projectorsModal = document.getElementById('homeProjectorsModal');
|
||
const projectorsClose = document.getElementById('homeProjectorsClose');
|
||
const projectorsTitlebar = projectorsModal ? projectorsModal.querySelector('.home-modal__titlebar') : null;
|
||
const docCameraBtn = document.getElementById('homeDocCameraBtn');
|
||
const docCameraModal = document.getElementById('homeDocCameraModal');
|
||
const docCameraClose = document.getElementById('homeDocCameraClose');
|
||
const docCameraTitlebar = docCameraModal ? docCameraModal.querySelector('.home-modal__titlebar') : null;
|
||
const usersBtn = document.getElementById('homeUsersBtn');
|
||
const usersModal = document.getElementById('homeUsersModal');
|
||
const usersClose = document.getElementById('homeUsersClose');
|
||
const usersTitlebar = usersModal ? usersModal.querySelector('.home-modal__titlebar') : null;
|
||
const usersAddToggle = document.getElementById('usersAddToggle');
|
||
const usersAddForm = document.getElementById('usersAddForm');
|
||
const addDeviceBtn = document.getElementById('homeAddDeviceBtn');
|
||
const addDeviceModal = document.getElementById('homeAddDeviceModal');
|
||
const addDeviceClose = document.getElementById('homeAddDeviceClose');
|
||
const addDeviceTitlebar = addDeviceModal ? addDeviceModal.querySelector('.home-modal__titlebar') : null;
|
||
const pcComponentsBtn = document.getElementById('homePcComponentsBtn');
|
||
const pcComponentsModal = document.getElementById('homePcComponentsModal');
|
||
const pcComponentsClose = document.getElementById('homePcComponentsClose');
|
||
const pcComponentsTitlebar = pcComponentsModal ? pcComponentsModal.querySelector('.home-modal__titlebar') : null;
|
||
const pcComponentsLookupInput = document.getElementById('pcComponentsLookupInput');
|
||
const pcComponentsLookupStatus = document.getElementById('pcComponentsLookupStatus');
|
||
const pcComponentsAssignForm = document.getElementById('pcComponentsAssignForm');
|
||
const pcComponentsAssignId = document.getElementById('pcComponentsAssignId');
|
||
const pcComponentsFoundModel = document.getElementById('pcComponentsFoundModel');
|
||
const pcComponentsFoundType = document.getElementById('pcComponentsFoundType');
|
||
const pcComponentsFoundQty = document.getElementById('pcComponentsFoundQty');
|
||
const pcComponentsAssignQty = document.getElementById('pcComponentsAssignQty');
|
||
const pcComponentsAddForm = document.getElementById('pcComponentsAddForm');
|
||
const pcComponentsAddBarcode = document.getElementById('pcComponentsAddBarcode');
|
||
const pcComponentsAddModel = document.getElementById('pcComponentsAddModel');
|
||
const pcComponentsAddQty = document.getElementById('pcComponentsAddQty');
|
||
const addDeviceType = document.getElementById('addDeviceType');
|
||
const addDeviceForms = addDeviceModal ? addDeviceModal.querySelectorAll('[data-add-type]') : [];
|
||
const homeComputerSpecsToggle = document.getElementById('homeComputerSpecsToggle');
|
||
const homeComputerSpecs = document.getElementById('homeComputerSpecs');
|
||
const homeDeviceBrandSelect = document.getElementById('homeDeviceBrandSelect');
|
||
const homeDeviceBrandCustom = document.getElementById('homeDeviceBrandCustom');
|
||
const homeDeviceBrandValue = document.getElementById('homeDeviceBrandValue');
|
||
const homeComputerBrandSelect = document.getElementById('homeComputerBrandSelect');
|
||
const homeComputerBrandCustom = document.getElementById('homeComputerBrandCustom');
|
||
const homeComputerBrandValue = document.getElementById('homeComputerBrandValue');
|
||
const homeProjectorBrandSelect = document.getElementById('homeProjectorBrandSelect');
|
||
const homeProjectorBrandCustom = document.getElementById('homeProjectorBrandCustom');
|
||
const homeProjectorBrandValue = document.getElementById('homeProjectorBrandValue');
|
||
const homeDocBrandSelect = document.getElementById('homeDocBrandSelect');
|
||
const homeDocBrandCustom = document.getElementById('homeDocBrandCustom');
|
||
const homeDocBrandValue = document.getElementById('homeDocBrandValue');
|
||
const homeProjectorComputerSelect = document.getElementById('homeProjectorComputerSelect');
|
||
const homeProjectorComputerInv = document.getElementById('homeProjectorComputerInv');
|
||
const homeNotesLayer = document.getElementById('homeNotesLayer');
|
||
const initialHomeNotes = {{ home_notes|tojson }};
|
||
let homeNotesMaxZ = initialHomeNotes.reduce((maxValue, note) => Math.max(maxValue, Number(note.z) || 1), 1);
|
||
const docCameraInv = document.getElementById('docCameraInv');
|
||
const docCameraInfoInv = document.getElementById('docCameraInfoInv');
|
||
const docCameraInfoBrand = document.getElementById('docCameraInfoBrand');
|
||
const docCameraInfoSerial = document.getElementById('docCameraInfoSerial');
|
||
const docCameraInfoDate = document.getElementById('docCameraInfoDate');
|
||
const docCameraInfoCabinet = document.getElementById('docCameraInfoCabinet');
|
||
const docCameraCabinetForm = document.getElementById('docCameraCabinetForm');
|
||
const docCameraCabinetSelect = document.getElementById('docCameraCabinetSelect');
|
||
const docCameraId = document.getElementById('docCameraId');
|
||
const searchCabinetViewSelect = document.getElementById('searchCabinetViewSelect');
|
||
const cabinetInfoTitle = document.getElementById('cabinetInfoTitle');
|
||
const cabinetDevicesWrap = document.getElementById('cabinetDevicesWrap');
|
||
const cabinetDevicesList = document.getElementById('cabinetDevicesList');
|
||
const cabinetComputersWrap = document.getElementById('cabinetComputersWrap');
|
||
const cabinetComputersList = document.getElementById('cabinetComputersList');
|
||
const cabinetProjectorsWrap = document.getElementById('cabinetProjectorsWrap');
|
||
const cabinetProjectorsList = document.getElementById('cabinetProjectorsList');
|
||
const cabinetDocsWrap = document.getElementById('cabinetDocsWrap');
|
||
const cabinetDocsList = document.getElementById('cabinetDocsList');
|
||
const cabinetEmptyNote = document.getElementById('cabinetEmptyNote');
|
||
const searchInvInput = document.getElementById('searchInvInput');
|
||
const searchInfoInv = document.getElementById('searchInfoInv');
|
||
const searchInfoType = document.getElementById('searchInfoType');
|
||
const searchInfoBrand = document.getElementById('searchInfoBrand');
|
||
const searchInfoSerial = document.getElementById('searchInfoSerial');
|
||
const searchInfoDate = document.getElementById('searchInfoDate');
|
||
const searchInfoCabinet = document.getElementById('searchInfoCabinet');
|
||
const searchConsumablesWrap = document.getElementById('searchConsumablesWrap');
|
||
const searchConsumablesList = document.getElementById('searchConsumablesList');
|
||
const searchSpecsWrap = document.getElementById('searchSpecsWrap');
|
||
const searchSpecCpu = document.getElementById('searchSpecCpu');
|
||
const searchSpecGpu = document.getElementById('searchSpecGpu');
|
||
const searchSpecRam = document.getElementById('searchSpecRam');
|
||
const searchSpecStorage = document.getElementById('searchSpecStorage');
|
||
const searchSpecOs = document.getElementById('searchSpecOs');
|
||
const searchSpecMotherboard = document.getElementById('searchSpecMotherboard');
|
||
const searchKitWrap = document.getElementById('searchKitWrap');
|
||
const searchKitProjectorTitle = document.getElementById('searchKitProjectorTitle');
|
||
const searchKitProjectorList = document.getElementById('searchKitProjectorList');
|
||
const searchKitBoardTitle = document.getElementById('searchKitBoardTitle');
|
||
const searchKitBoardList = document.getElementById('searchKitBoardList');
|
||
const searchKitComputerTitle = document.getElementById('searchKitComputerTitle');
|
||
const searchKitComputerList = document.getElementById('searchKitComputerList');
|
||
const projectorsInv = document.getElementById('projectorsInv');
|
||
const projectorsInfoInv = document.getElementById('projectorsInfoInv');
|
||
const projectorsInfoType = document.getElementById('projectorsInfoType');
|
||
const projectorsInfoBrand = document.getElementById('projectorsInfoBrand');
|
||
const projectorsInfoSerial = document.getElementById('projectorsInfoSerial');
|
||
const projectorsInfoDate = document.getElementById('projectorsInfoDate');
|
||
const projectorsInfoCabinet = document.getElementById('projectorsInfoCabinet');
|
||
const projectorsCabinetForm = document.getElementById('projectorsCabinetForm');
|
||
const projectorsCabinetSelect = document.getElementById('projectorsCabinetSelect');
|
||
const projectorsId = document.getElementById('projectorsId');
|
||
const projectorsKitWrap = document.getElementById('projectorsKitWrap');
|
||
const kitProjectorTitle = document.getElementById('kitProjectorTitle');
|
||
const kitProjectorList = document.getElementById('kitProjectorList');
|
||
const kitBoardTitle = document.getElementById('kitBoardTitle');
|
||
const kitBoardList = document.getElementById('kitBoardList');
|
||
const kitComputerTitle = document.getElementById('kitComputerTitle');
|
||
const kitComputerList = document.getElementById('kitComputerList');
|
||
const computersInv = document.getElementById('computersInv');
|
||
const computersInfoInv = document.getElementById('computersInfoInv');
|
||
const computersInfoBrand = document.getElementById('computersInfoBrand');
|
||
const computersInfoSerial = document.getElementById('computersInfoSerial');
|
||
const computersInfoDate = document.getElementById('computersInfoDate');
|
||
const computersInfoCabinet = document.getElementById('computersInfoCabinet');
|
||
const computersCabinetForm = document.getElementById('computersCabinetForm');
|
||
const computersCabinetSelect = document.getElementById('computersCabinetSelect');
|
||
const computersId = document.getElementById('computersId');
|
||
const computersSpecsWrap = document.getElementById('computersSpecsWrap');
|
||
const computersSpecsList = computersSpecsWrap ? computersSpecsWrap.querySelector('.home-modal__specs-list') : null;
|
||
const computersSpecCpu = document.getElementById('computersSpecCpu');
|
||
const computersSpecGpu = document.getElementById('computersSpecGpu');
|
||
const computersSpecRam = document.getElementById('computersSpecRam');
|
||
const computersSpecStorage = document.getElementById('computersSpecStorage');
|
||
const computersSpecOs = document.getElementById('computersSpecOs');
|
||
const computersSpecMotherboard = document.getElementById('computersSpecMotherboard');
|
||
const printersInv = document.getElementById('printersInv');
|
||
const printersInfoInv = document.getElementById('printersInfoInv');
|
||
const printersInfoBrand = document.getElementById('printersInfoBrand');
|
||
const printersInfoSerial = document.getElementById('printersInfoSerial');
|
||
const printersInfoDate = document.getElementById('printersInfoDate');
|
||
const printersInfoCabinet = document.getElementById('printersInfoCabinet');
|
||
const printersCabinetForm = document.getElementById('printersCabinetForm');
|
||
const printersCabinetSelect = document.getElementById('printersCabinetSelect');
|
||
const printersId = document.getElementById('printersId');
|
||
const printersConsumablesWrap = document.getElementById('printersConsumablesWrap');
|
||
const printersConsumablesList = document.getElementById('printersConsumablesList');
|
||
const photoBarcode = document.getElementById('photoBarcode');
|
||
const photoModelText = document.getElementById('photoModelText');
|
||
const photoStockText = document.getElementById('photoStockText');
|
||
const photoDeviceWrap = document.getElementById('photoDeviceWrap');
|
||
const photoDevice = document.getElementById('photoDevice');
|
||
const photoAddTitle = document.getElementById('photoAddTitle');
|
||
const photoAddForm = document.getElementById('photoAddForm');
|
||
const photoAddBarcode = document.getElementById('photoAddBarcode');
|
||
const photoAddModel = document.getElementById('photoAddModel');
|
||
const photoAddTypeSelect = document.getElementById('photoAddTypeSelect');
|
||
const photoAddTypeCustom = document.getElementById('photoAddTypeCustom');
|
||
const photoAddQty = document.getElementById('photoAddQty');
|
||
const modalBarcode = document.getElementById('modalBarcode');
|
||
const modalCartridgeName = document.getElementById('modalCartridgeName');
|
||
const modalCartridgeStock = document.getElementById('modalCartridgeStock');
|
||
const modalCabinet = document.getElementById('modalCabinet');
|
||
const modalQuantity = document.getElementById('modalQuantity');
|
||
const modalIssueSubmit = document.getElementById('modalIssueSubmit');
|
||
const modalAddNotice = document.getElementById('modalAddNotice');
|
||
const modalAddFields = document.getElementById('modalAddFields');
|
||
const modalAddModel = document.getElementById('modalAddModel');
|
||
const modalAddQty = document.getElementById('modalAddQty');
|
||
const modalAddActions = document.getElementById('modalAddActions');
|
||
const cartridgeCabinet = document.getElementById('cartridgeCabinet');
|
||
const cartridgeIssueSubmit = document.getElementById('cartridgeIssueSubmit');
|
||
const defaultModalCabinetOptions = modalCabinet ? modalCabinet.innerHTML : '';
|
||
const defaultCartridgeCabinetOptions = cartridgeCabinet ? cartridgeCabinet.innerHTML : '';
|
||
|
||
function togglePhotoAdd(show, barcode) {
|
||
if (!photoAddTitle || !photoAddForm) return;
|
||
const action = show ? 'remove' : 'add';
|
||
photoAddTitle.classList[action]('d-none');
|
||
photoAddForm.classList[action]('d-none');
|
||
if (show) {
|
||
if (photoAddBarcode) photoAddBarcode.value = barcode || '';
|
||
if (photoAddQty && !photoAddQty.value) photoAddQty.value = '1';
|
||
}
|
||
}
|
||
|
||
function syncPhotoAddType() {
|
||
if (!photoAddTypeSelect || !photoAddTypeCustom) return;
|
||
if (photoAddTypeSelect.value === '__custom__') {
|
||
photoAddTypeCustom.classList.remove('d-none');
|
||
photoAddTypeCustom.focus();
|
||
} else {
|
||
photoAddTypeCustom.classList.add('d-none');
|
||
photoAddTypeCustom.value = '';
|
||
}
|
||
}
|
||
|
||
function hideAll() {
|
||
cartCard.classList.add('d-none');
|
||
consCard.classList.add('d-none');
|
||
addCard.classList.add('d-none');
|
||
deviceCard.classList.add('d-none');
|
||
addCartridgeForm.classList.add('d-none');
|
||
addConsumableForm.classList.add('d-none');
|
||
}
|
||
|
||
function setCabinetOptions(selectEl, submitBtn, cabinets) {
|
||
if (!selectEl) return;
|
||
const values = Array.isArray(cabinets) ? cabinets.filter(Boolean) : [];
|
||
if (!values.length) {
|
||
selectEl.innerHTML = '<option value="">Нет привязанных кабинетов</option>';
|
||
selectEl.value = '';
|
||
selectEl.disabled = true;
|
||
if (submitBtn) submitBtn.disabled = true;
|
||
return;
|
||
}
|
||
selectEl.innerHTML = '';
|
||
const placeholder = document.createElement('option');
|
||
placeholder.value = '';
|
||
placeholder.textContent = 'Выберите кабинет...';
|
||
selectEl.appendChild(placeholder);
|
||
values.forEach((name) => {
|
||
const option = document.createElement('option');
|
||
option.value = name;
|
||
option.textContent = name;
|
||
selectEl.appendChild(option);
|
||
});
|
||
selectEl.value = '';
|
||
selectEl.disabled = false;
|
||
if (submitBtn) submitBtn.disabled = false;
|
||
}
|
||
|
||
function resetCabinetOptions() {
|
||
if (modalCabinet) {
|
||
modalCabinet.innerHTML = defaultModalCabinetOptions;
|
||
modalCabinet.disabled = false;
|
||
}
|
||
if (cartridgeCabinet) {
|
||
cartridgeCabinet.innerHTML = defaultCartridgeCabinetOptions;
|
||
cartridgeCabinet.disabled = false;
|
||
}
|
||
if (modalIssueSubmit) modalIssueSubmit.disabled = false;
|
||
if (cartridgeIssueSubmit) cartridgeIssueSubmit.disabled = false;
|
||
}
|
||
|
||
function applyAllowedCabinets(cabinets) {
|
||
setCabinetOptions(modalCabinet, modalIssueSubmit, cabinets);
|
||
setCabinetOptions(cartridgeCabinet, cartridgeIssueSubmit, cabinets);
|
||
}
|
||
|
||
function showAdd(kind, barcode) {
|
||
addCard.classList.remove('d-none');
|
||
addKindSelect.value = kind || '';
|
||
addCartridgeBarcode.value = barcode || '';
|
||
addConsumableBarcode.value = barcode || '';
|
||
if (kind === 'cartridge') {
|
||
addCartridgeForm.classList.remove('d-none');
|
||
addConsumableForm.classList.add('d-none');
|
||
} else if (kind === 'consumable') {
|
||
addConsumableForm.classList.remove('d-none');
|
||
addCartridgeForm.classList.add('d-none');
|
||
} else {
|
||
addCartridgeForm.classList.add('d-none');
|
||
addConsumableForm.classList.add('d-none');
|
||
}
|
||
}
|
||
|
||
async function lookup() {
|
||
const barcode = (input.value || '').trim();
|
||
if (!barcode) {
|
||
hint.textContent = '';
|
||
hideAll();
|
||
resetCabinetOptions();
|
||
return;
|
||
}
|
||
hint.textContent = 'Ищу...';
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(barcode)}`);
|
||
if (!res.ok) {
|
||
hint.textContent = 'Ошибка запроса';
|
||
hideAll();
|
||
return;
|
||
}
|
||
const data = await res.json();
|
||
if (!data.found) {
|
||
hint.textContent = 'Расходный материал не найден. Добавить?';
|
||
cartCard.classList.add('d-none');
|
||
consCard.classList.add('d-none');
|
||
deviceCard.classList.add('d-none');
|
||
showAdd('consumable', barcode);
|
||
resetCabinetOptions();
|
||
return;
|
||
}
|
||
if (data.kind === 'device' || data.kind === 'computer' || data.kind === 'projector' || data.kind === 'board') {
|
||
const kind = data.kind;
|
||
const titleMap = {
|
||
device: 'МФУ / Принтер',
|
||
computer: 'Компьютер / Ноутбук',
|
||
projector: 'Проектор',
|
||
board: 'Доска',
|
||
};
|
||
const typeLabel = data.type_label || data.kit_type_label || data.type || '';
|
||
deviceCardTitle.textContent = typeLabel || titleMap[kind] || 'Устройство';
|
||
deviceInventory.textContent = data.inventory_number || '';
|
||
deviceModel.textContent = data.model || data.projector_model || '';
|
||
deviceBrand.textContent = data.brand || '—';
|
||
deviceSerial.textContent = data.serial_number || data.projector_serial || '—';
|
||
deviceCabinet.textContent = data.cabinet_name || '—';
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
deviceDate.textContent = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
} else {
|
||
deviceDate.textContent = '—';
|
||
}
|
||
equipmentId.value = data.id || '';
|
||
equipmentCabinetSelect.value = data.cabinet_id || '';
|
||
if (kind === 'device') {
|
||
equipmentCabinetForm.action = '/devices/update_cabinet';
|
||
} else if (kind === 'computer') {
|
||
equipmentCabinetForm.action = '/computers/update_cabinet';
|
||
} else if (kind === 'projector') {
|
||
equipmentCabinetForm.action = '/projectors/update_cabinet';
|
||
} else if (kind === 'board') {
|
||
equipmentCabinetForm.action = '/projectors/update_cabinet';
|
||
}
|
||
if (kind === 'projector') {
|
||
deviceBrandWrap.classList.remove('d-none');
|
||
deviceSerialWrap.classList.remove('d-none');
|
||
} else if (kind === 'board') {
|
||
deviceBrandWrap.classList.add('d-none');
|
||
deviceSerialWrap.classList.remove('d-none');
|
||
} else {
|
||
deviceBrandWrap.classList.remove('d-none');
|
||
deviceSerialWrap.classList.remove('d-none');
|
||
}
|
||
deviceConsumablesList.innerHTML = '';
|
||
const items = Array.isArray(data.consumables) ? data.consumables : [];
|
||
if (kind === 'device' && items.length) {
|
||
deviceConsumablesSection.classList.remove('d-none');
|
||
items.forEach(item => {
|
||
const li = document.createElement('li');
|
||
const label = item.type ? `${item.type}: ` : '';
|
||
li.textContent = `${label}${item.model || ''}`.trim();
|
||
deviceConsumablesList.appendChild(li);
|
||
});
|
||
} else {
|
||
deviceConsumablesSection.classList.add('d-none');
|
||
}
|
||
if (kind === 'computer') {
|
||
const cpuLabel = [data.cpu_brand, data.cpu_model].filter(Boolean).join(' ');
|
||
|
||
function formatGB(value) {
|
||
const raw = (value || '').toString().trim();
|
||
if (!raw) return '—';
|
||
const lower = raw.toLowerCase();
|
||
const num = parseFloat(raw.replace(',', '.'));
|
||
if (Number.isNaN(num)) return raw;
|
||
if (lower.includes('mb') || lower.includes('мб')) {
|
||
const gb = num / 1024;
|
||
const label = gb % 1 === 0 ? gb.toFixed(0) : gb.toFixed(1);
|
||
return `${label}GB`;
|
||
}
|
||
if (lower.includes('gb') || lower.includes('гб')) {
|
||
const label = num % 1 === 0 ? num.toFixed(0) : num.toFixed(1);
|
||
return `${label}GB`;
|
||
}
|
||
const label = num % 1 === 0 ? num.toFixed(0) : num.toFixed(1);
|
||
return `${label}GB`;
|
||
}
|
||
|
||
const memoryLabel = [formatGB(data.memory_size), data.memory_type].filter(v => v && v !== '—').join(' ');
|
||
compCpu.textContent = cpuLabel || '—';
|
||
compGpu.textContent = data.gpu_model || '—';
|
||
compMemory.textContent = memoryLabel || '—';
|
||
compStorage.textContent = formatGB(data.storage_size);
|
||
compMotherboard.textContent = data.motherboard || '—';
|
||
compOs.textContent = data.os || '—';
|
||
if (data.type === 'laptop') {
|
||
compMotherboardRow.classList.add('d-none');
|
||
} else {
|
||
compMotherboardRow.classList.remove('d-none');
|
||
}
|
||
computerSpecsSection.classList.remove('d-none');
|
||
} else {
|
||
computerSpecsSection.classList.add('d-none');
|
||
}
|
||
if (kind === 'projector' && data.kit_type === 'kit') {
|
||
projInv.textContent = data.projector_inventory_number || '—';
|
||
projBrand.textContent = data.projector_brand || data.brand || '—';
|
||
projSerial.textContent = data.projector_serial || '—';
|
||
boardModel.textContent = data.board_model || '—';
|
||
boardInv.textContent = data.board_inventory_number || '—';
|
||
boardBrand.textContent = data.board_brand || data.brand || '—';
|
||
boardSerial.textContent = data.board_serial || '—';
|
||
kitCompInv.textContent = data.computer_inventory_number || '—';
|
||
kitCompBrand.textContent = data.computer_brand || '—';
|
||
kitCompModel.textContent = data.computer_model || '—';
|
||
kitCompSerial.textContent = data.computer_serial || '—';
|
||
projectorKitSection.classList.remove('d-none');
|
||
} else {
|
||
projectorKitSection.classList.add('d-none');
|
||
}
|
||
if (kind === 'projector' && data.kit_type === 'display') {
|
||
projCompModel.textContent = data.computer_model || '—';
|
||
projCompInv.textContent = data.computer_inventory_number || '—';
|
||
projectorComputerSection.classList.remove('d-none');
|
||
} else {
|
||
projectorComputerSection.classList.add('d-none');
|
||
}
|
||
cartCard.classList.add('d-none');
|
||
consCard.classList.add('d-none');
|
||
addCard.classList.add('d-none');
|
||
deviceCard.classList.remove('d-none');
|
||
resetCabinetOptions();
|
||
hint.textContent = `Найдено: ${data.inventory_number || ''}`;
|
||
} else if (data.kind === 'cartridge') {
|
||
cartModel.value = data.model || '';
|
||
cartBarcode.value = data.barcode || barcode;
|
||
consBarcode.value = '';
|
||
consModel.value = '';
|
||
applyAllowedCabinets(data.allowed_cabinets || []);
|
||
deviceCard.classList.add('d-none');
|
||
consCard.classList.add('d-none');
|
||
addCard.classList.add('d-none');
|
||
cartCard.classList.remove('d-none');
|
||
hint.textContent = `Картридж: ${data.model || ''}`;
|
||
} else if (data.kind === 'consumable') {
|
||
consModel.value = data.model || '';
|
||
consBarcode.value = data.barcode || barcode;
|
||
cartBarcode.value = '';
|
||
cartModel.value = '';
|
||
resetCabinetOptions();
|
||
deviceCard.classList.add('d-none');
|
||
cartCard.classList.add('d-none');
|
||
addCard.classList.add('d-none');
|
||
consCard.classList.remove('d-none');
|
||
hint.textContent = `Расходник: ${data.model || ''}`;
|
||
} else {
|
||
hint.textContent = 'Неизвестный тип';
|
||
hideAll();
|
||
resetCabinetOptions();
|
||
}
|
||
} catch (e) {
|
||
hint.textContent = 'Ошибка сети';
|
||
hideAll();
|
||
resetCabinetOptions();
|
||
}
|
||
}
|
||
|
||
addKindSelect.addEventListener('change', () => {
|
||
const kind = addKindSelect.value;
|
||
const barcode = (input.value || '').trim();
|
||
showAdd(kind, barcode);
|
||
});
|
||
|
||
consumableTypeSelect.addEventListener('change', () => {
|
||
if (consumableTypeSelect.value === '__custom__') {
|
||
consumableTypeCustom.classList.remove('d-none');
|
||
consumableTypeCustom.focus();
|
||
} else {
|
||
consumableTypeCustom.classList.add('d-none');
|
||
consumableTypeCustom.value = '';
|
||
}
|
||
});
|
||
|
||
if (input) {
|
||
let timer = null;
|
||
input.addEventListener('input', () => {
|
||
if (timer) window.clearTimeout(timer);
|
||
timer = window.setTimeout(lookup, 120);
|
||
});
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
lookup();
|
||
}
|
||
});
|
||
}
|
||
|
||
function resetPcComponentsUi() {
|
||
if (pcComponentsLookupStatus) pcComponentsLookupStatus.textContent = '';
|
||
if (pcComponentsAssignForm) pcComponentsAssignForm.classList.add('d-none');
|
||
if (pcComponentsAddForm) pcComponentsAddForm.classList.add('d-none');
|
||
if (pcComponentsAssignId) pcComponentsAssignId.value = '';
|
||
if (pcComponentsFoundModel) pcComponentsFoundModel.textContent = '—';
|
||
if (pcComponentsFoundType) pcComponentsFoundType.textContent = '—';
|
||
if (pcComponentsFoundQty) pcComponentsFoundQty.textContent = '0';
|
||
if (pcComponentsAssignQty) pcComponentsAssignQty.value = '1';
|
||
if (pcComponentsAddQty) pcComponentsAddQty.value = '1';
|
||
}
|
||
|
||
async function lookupPcComponent() {
|
||
if (!pcComponentsLookupInput || !pcComponentsLookupStatus) return;
|
||
const q = (pcComponentsLookupInput.value || '').trim();
|
||
if (!q) {
|
||
resetPcComponentsUi();
|
||
return;
|
||
}
|
||
pcComponentsLookupStatus.textContent = 'Ищу...';
|
||
try {
|
||
const res = await fetch(`/api/component_lookup?q=${encodeURIComponent(q)}`);
|
||
if (!res.ok) {
|
||
resetPcComponentsUi();
|
||
pcComponentsLookupStatus.textContent = 'Ошибка поиска';
|
||
return;
|
||
}
|
||
const data = await res.json();
|
||
if (data && data.found && data.component) {
|
||
const comp = data.component;
|
||
if (pcComponentsAssignId) pcComponentsAssignId.value = comp.id || '';
|
||
if (pcComponentsFoundModel) pcComponentsFoundModel.textContent = comp.model || '—';
|
||
if (pcComponentsFoundType) pcComponentsFoundType.textContent = comp.component_type || '—';
|
||
if (pcComponentsFoundQty) pcComponentsFoundQty.textContent = String(comp.quantity ?? 0);
|
||
if (pcComponentsLookupStatus) pcComponentsLookupStatus.textContent = '';
|
||
if (pcComponentsAssignForm) pcComponentsAssignForm.classList.remove('d-none');
|
||
if (pcComponentsAddForm) pcComponentsAddForm.classList.add('d-none');
|
||
} else {
|
||
if (pcComponentsLookupStatus) pcComponentsLookupStatus.textContent = 'Неизвестный штрихкод/модель';
|
||
if (pcComponentsAssignForm) pcComponentsAssignForm.classList.add('d-none');
|
||
if (pcComponentsAddForm) pcComponentsAddForm.classList.remove('d-none');
|
||
if (pcComponentsAddBarcode) pcComponentsAddBarcode.value = q;
|
||
}
|
||
} catch (e) {
|
||
resetPcComponentsUi();
|
||
if (pcComponentsLookupStatus) pcComponentsLookupStatus.textContent = 'Ошибка сети';
|
||
}
|
||
}
|
||
|
||
if (pcComponentsLookupInput) {
|
||
let pcLookupTimer = null;
|
||
pcComponentsLookupInput.addEventListener('input', () => {
|
||
resetPcComponentsUi();
|
||
if (pcLookupTimer) window.clearTimeout(pcLookupTimer);
|
||
const q = (pcComponentsLookupInput.value || '').trim();
|
||
if (q.length < 3) return;
|
||
pcLookupTimer = window.setTimeout(lookupPcComponent, 600);
|
||
});
|
||
pcComponentsLookupInput.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupPcComponent();
|
||
}
|
||
});
|
||
}
|
||
|
||
function toggleDesktop(show) {
|
||
if (!desktopModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : desktopModal.classList.contains('hidden');
|
||
desktopModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
|
||
function togglePhoto(show) {
|
||
if (!photoModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : photoModal.classList.contains('hidden');
|
||
photoModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function toggleSearchInv(show) {
|
||
if (!searchInvModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : searchInvModal.classList.contains('hidden');
|
||
searchInvModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function togglePrinters(show) {
|
||
if (!printersModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : printersModal.classList.contains('hidden');
|
||
printersModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function toggleComputers(show) {
|
||
if (!computersModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : computersModal.classList.contains('hidden');
|
||
computersModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function toggleProjectors(show) {
|
||
if (!projectorsModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : projectorsModal.classList.contains('hidden');
|
||
projectorsModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function toggleDocCamera(show) {
|
||
if (!docCameraModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : docCameraModal.classList.contains('hidden');
|
||
docCameraModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function toggleAddDevice(show) {
|
||
if (!addDeviceModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : addDeviceModal.classList.contains('hidden');
|
||
addDeviceModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
function togglePcComponents(show) {
|
||
if (!pcComponentsModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : pcComponentsModal.classList.contains('hidden');
|
||
pcComponentsModal.classList.toggle('hidden', !shouldShow);
|
||
if (shouldShow) {
|
||
resetPcComponentsUi();
|
||
if (pcComponentsLookupInput) {
|
||
pcComponentsLookupInput.value = '';
|
||
pcComponentsLookupInput.focus();
|
||
}
|
||
}
|
||
}
|
||
function toggleUsers(show) {
|
||
if (!usersModal) return;
|
||
const shouldShow = typeof show === 'boolean' ? show : usersModal.classList.contains('hidden');
|
||
usersModal.classList.toggle('hidden', !shouldShow);
|
||
}
|
||
|
||
if (desktopBtn) {
|
||
desktopBtn.addEventListener('click', () => toggleDesktop(true));
|
||
}
|
||
if (desktopClose) {
|
||
desktopClose.addEventListener('click', () => toggleDesktop(false));
|
||
}
|
||
if (photoBtn) {
|
||
photoBtn.addEventListener('click', () => togglePhoto(true));
|
||
}
|
||
if (photoClose) {
|
||
photoClose.addEventListener('click', () => togglePhoto(false));
|
||
}
|
||
if (searchInvBtn) {
|
||
searchInvBtn.addEventListener('click', () => toggleSearchInv(true));
|
||
}
|
||
if (searchInvClose) {
|
||
searchInvClose.addEventListener('click', () => toggleSearchInv(false));
|
||
}
|
||
if (printersBtn) {
|
||
printersBtn.addEventListener('click', () => togglePrinters(true));
|
||
}
|
||
if (printersClose) {
|
||
printersClose.addEventListener('click', () => togglePrinters(false));
|
||
}
|
||
if (computersBtn) {
|
||
computersBtn.addEventListener('click', () => toggleComputers(true));
|
||
}
|
||
if (computersClose) {
|
||
computersClose.addEventListener('click', () => toggleComputers(false));
|
||
}
|
||
if (projectorsBtn) {
|
||
projectorsBtn.addEventListener('click', () => toggleProjectors(true));
|
||
}
|
||
if (projectorsClose) {
|
||
projectorsClose.addEventListener('click', () => toggleProjectors(false));
|
||
}
|
||
if (docCameraBtn) {
|
||
docCameraBtn.addEventListener('click', () => toggleDocCamera(true));
|
||
}
|
||
if (docCameraClose) {
|
||
docCameraClose.addEventListener('click', () => toggleDocCamera(false));
|
||
}
|
||
if (usersBtn) {
|
||
usersBtn.addEventListener('click', () => toggleUsers(true));
|
||
}
|
||
if (usersClose) {
|
||
usersClose.addEventListener('click', () => toggleUsers(false));
|
||
}
|
||
if (usersAddToggle && usersAddForm) {
|
||
usersAddToggle.addEventListener('click', () => {
|
||
usersAddForm.classList.toggle('d-none');
|
||
if (!usersAddForm.classList.contains('d-none')) {
|
||
const input = usersAddForm.querySelector('input[name="last_name"]');
|
||
if (input) input.focus();
|
||
}
|
||
});
|
||
}
|
||
if (addDeviceBtn) {
|
||
addDeviceBtn.addEventListener('click', () => toggleAddDevice(true));
|
||
}
|
||
if (addDeviceClose) {
|
||
addDeviceClose.addEventListener('click', () => toggleAddDevice(false));
|
||
}
|
||
if (pcComponentsBtn) {
|
||
pcComponentsBtn.addEventListener('click', () => togglePcComponents(true));
|
||
}
|
||
if (pcComponentsClose) {
|
||
pcComponentsClose.addEventListener('click', () => togglePcComponents(false));
|
||
}
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
toggleDesktop(false);
|
||
togglePhoto(false);
|
||
toggleSearchInv(false);
|
||
togglePrinters(false);
|
||
toggleComputers(false);
|
||
toggleProjectors(false);
|
||
toggleDocCamera(false);
|
||
toggleUsers(false);
|
||
toggleAddDevice(false);
|
||
togglePcComponents(false);
|
||
}
|
||
});
|
||
|
||
async function lookupModalBarcode() {
|
||
if (!modalBarcode || !modalCartridgeName) return;
|
||
const barcode = (modalBarcode.value || '').trim();
|
||
if (!barcode) {
|
||
modalCartridgeName.textContent = '';
|
||
if (modalCartridgeStock) modalCartridgeStock.textContent = '';
|
||
resetCabinetOptions();
|
||
if (modalAddNotice) modalAddNotice.classList.add('d-none');
|
||
if (modalAddFields) modalAddFields.classList.add('d-none');
|
||
if (modalAddActions) modalAddActions.classList.add('d-none');
|
||
if (modalAddModel) modalAddModel.value = '';
|
||
return;
|
||
}
|
||
modalCartridgeName.textContent = 'Ищу...';
|
||
if (modalCartridgeStock) modalCartridgeStock.textContent = '';
|
||
if (modalAddNotice) modalAddNotice.classList.add('d-none');
|
||
if (modalAddFields) modalAddFields.classList.add('d-none');
|
||
if (modalAddActions) modalAddActions.classList.add('d-none');
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(barcode)}`);
|
||
if (!res.ok) {
|
||
modalCartridgeName.textContent = 'Ошибка запроса';
|
||
if (modalCartridgeStock) modalCartridgeStock.textContent = '';
|
||
return;
|
||
}
|
||
const data = await res.json();
|
||
if (!data.found || data.kind !== 'cartridge') {
|
||
modalCartridgeName.textContent = 'Картридж не найден';
|
||
resetCabinetOptions();
|
||
if (modalAddNotice) modalAddNotice.classList.remove('d-none');
|
||
if (modalAddFields) modalAddFields.classList.remove('d-none');
|
||
if (modalAddActions) modalAddActions.classList.remove('d-none');
|
||
if (modalAddQty && !modalAddQty.value) modalAddQty.value = '1';
|
||
if (modalCartridgeStock) modalCartridgeStock.textContent = '';
|
||
return;
|
||
}
|
||
modalCartridgeName.textContent = `Картридж: ${data.model || ''}`.trim();
|
||
applyAllowedCabinets(data.allowed_cabinets || []);
|
||
if (modalCartridgeStock) {
|
||
const qty = typeof data.quantity === 'number' ? data.quantity : '';
|
||
modalCartridgeStock.textContent = qty !== '' ? `Остаток: ${qty}` : '';
|
||
}
|
||
if (modalQuantity && !modalQuantity.value) {
|
||
modalQuantity.value = '1';
|
||
}
|
||
} catch (e) {
|
||
modalCartridgeName.textContent = 'Ошибка сети';
|
||
resetCabinetOptions();
|
||
}
|
||
}
|
||
|
||
if (modalBarcode) {
|
||
let modalTimer = null;
|
||
modalBarcode.addEventListener('input', () => {
|
||
if (modalTimer) window.clearTimeout(modalTimer);
|
||
modalTimer = window.setTimeout(lookupModalBarcode, 120);
|
||
});
|
||
modalBarcode.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupModalBarcode();
|
||
}
|
||
});
|
||
}
|
||
|
||
async function loadConsumableDevices(barcode) {
|
||
if (!photoDevice) return;
|
||
photoDevice.innerHTML = '<option value="">Выберите устройство...</option>';
|
||
if (!barcode) return;
|
||
try {
|
||
const res = await fetch(`/api/consumable_devices?barcode=${encodeURIComponent(barcode)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
const items = Array.isArray(data.devices) ? data.devices : [];
|
||
if (!items.length) {
|
||
const opt = document.createElement('option');
|
||
opt.value = '';
|
||
opt.textContent = 'Нет привязанных устройств';
|
||
photoDevice.appendChild(opt);
|
||
return;
|
||
}
|
||
items.forEach(item => {
|
||
const opt = document.createElement('option');
|
||
const typeLabel = item.type === 'printer' ? 'Принтер' : 'МФУ';
|
||
opt.value = item.id;
|
||
opt.textContent = `${item.inventory_number || ''} — ${item.model || ''} (${typeLabel})`.trim();
|
||
photoDevice.appendChild(opt);
|
||
});
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
async function lookupPhotoBarcode() {
|
||
if (!photoBarcode || !photoDeviceWrap) return;
|
||
const barcode = (photoBarcode.value || '').trim();
|
||
if (!barcode) {
|
||
photoDeviceWrap.classList.add('d-none');
|
||
if (photoModelText) {
|
||
photoModelText.textContent = '';
|
||
photoModelText.classList.add('d-none');
|
||
}
|
||
if (photoStockText) {
|
||
photoStockText.textContent = '';
|
||
photoStockText.classList.add('d-none');
|
||
}
|
||
if (photoDevice) photoDevice.value = '';
|
||
togglePhotoAdd(false);
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(barcode)}`);
|
||
if (!res.ok) {
|
||
if (photoModelText) {
|
||
photoModelText.textContent = 'Ошибка запроса';
|
||
photoModelText.classList.remove('d-none');
|
||
}
|
||
photoDeviceWrap.classList.add('d-none');
|
||
if (photoStockText) {
|
||
photoStockText.textContent = '';
|
||
photoStockText.classList.add('d-none');
|
||
}
|
||
togglePhotoAdd(false);
|
||
return;
|
||
}
|
||
const data = await res.json();
|
||
if (!data.found || data.kind !== 'consumable') {
|
||
photoDeviceWrap.classList.add('d-none');
|
||
if (photoModelText) {
|
||
photoModelText.textContent = 'Расходный материал не найден. Добавить?';
|
||
photoModelText.classList.remove('d-none');
|
||
}
|
||
if (photoStockText) {
|
||
photoStockText.textContent = '';
|
||
photoStockText.classList.add('d-none');
|
||
}
|
||
if (photoDevice) photoDevice.value = '';
|
||
togglePhotoAdd(true, barcode);
|
||
return;
|
||
}
|
||
if (photoModelText) {
|
||
photoModelText.textContent = `Модель: ${data.model || ''}`.trim();
|
||
photoModelText.classList.remove('d-none');
|
||
}
|
||
if (photoStockText) {
|
||
const qty = typeof data.quantity === 'number' ? data.quantity : '';
|
||
photoStockText.textContent = qty !== '' ? `Остаток: ${qty}` : '';
|
||
photoStockText.classList.toggle('d-none', qty === '');
|
||
}
|
||
photoDeviceWrap.classList.remove('d-none');
|
||
togglePhotoAdd(false);
|
||
loadConsumableDevices(data.barcode || barcode);
|
||
} catch (e) {
|
||
photoDeviceWrap.classList.add('d-none');
|
||
if (photoModelText) {
|
||
photoModelText.textContent = 'Ошибка сети';
|
||
photoModelText.classList.remove('d-none');
|
||
}
|
||
if (photoStockText) {
|
||
photoStockText.textContent = '';
|
||
photoStockText.classList.add('d-none');
|
||
}
|
||
togglePhotoAdd(false);
|
||
}
|
||
}
|
||
|
||
if (photoBarcode) {
|
||
let photoTimer = null;
|
||
photoBarcode.addEventListener('input', () => {
|
||
if (photoTimer) window.clearTimeout(photoTimer);
|
||
photoTimer = window.setTimeout(lookupPhotoBarcode, 120);
|
||
});
|
||
photoBarcode.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupPhotoBarcode();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (photoAddTypeSelect && photoAddTypeCustom) {
|
||
photoAddTypeSelect.addEventListener('change', syncPhotoAddType);
|
||
syncPhotoAddType();
|
||
}
|
||
|
||
function setPrintersInfoVisible(visible) {
|
||
const action = visible ? 'remove' : 'add';
|
||
printersInfoInv.classList[action]('d-none');
|
||
printersInfoBrand.classList[action]('d-none');
|
||
printersInfoSerial.classList[action]('d-none');
|
||
printersInfoDate.classList[action]('d-none');
|
||
printersInfoCabinet.classList[action]('d-none');
|
||
printersCabinetForm.classList[action]('d-none');
|
||
printersConsumablesWrap.classList[action]('d-none');
|
||
}
|
||
|
||
async function lookupPrintersInv() {
|
||
if (!printersInv) return;
|
||
const inv = (printersInv.value || '').trim();
|
||
if (!inv) {
|
||
setPrintersInfoVisible(false);
|
||
printersInfoInv.textContent = '';
|
||
printersInfoBrand.textContent = '';
|
||
printersInfoSerial.textContent = '';
|
||
printersInfoDate.textContent = '';
|
||
printersInfoCabinet.textContent = '';
|
||
printersConsumablesList.innerHTML = '';
|
||
if (printersId) printersId.value = '';
|
||
if (printersCabinetSelect) printersCabinetSelect.value = '';
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(inv)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
const isPrintDevice = data.kind === 'device' && (data.type === 'printer' || data.type === 'mfp');
|
||
if (!data.found || !isPrintDevice) {
|
||
printersInfoInv.textContent = '';
|
||
printersInfoBrand.textContent = 'МФУ/Принтер не найден';
|
||
printersInfoSerial.textContent = '';
|
||
printersInfoDate.textContent = '';
|
||
printersInfoCabinet.textContent = '';
|
||
printersConsumablesList.innerHTML = '';
|
||
if (printersId) printersId.value = '';
|
||
if (printersCabinetSelect) printersCabinetSelect.value = '';
|
||
setPrintersInfoVisible(true);
|
||
return;
|
||
}
|
||
const brandModel = [data.brand, data.model].filter(Boolean).join(' ');
|
||
printersInfoInv.textContent = `Инвентарный номер: ${data.inventory_number || inv}`;
|
||
printersInfoBrand.textContent = `Бренд/Модель: ${brandModel || ''}`.trim();
|
||
printersInfoSerial.textContent = `Серийный номер: ${data.serial_number || '—'}`;
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
const label = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
printersInfoDate.textContent = `Дата ввода в эксплуатацию: ${label}`;
|
||
} else {
|
||
printersInfoDate.textContent = 'Дата ввода в эксплуатацию: —';
|
||
}
|
||
printersInfoCabinet.textContent = `Расположение: ${data.cabinet_name || '—'}`;
|
||
printersConsumablesList.innerHTML = '';
|
||
const items = Array.isArray(data.consumables) ? data.consumables : [];
|
||
if (items.length) {
|
||
items.forEach((item) => {
|
||
const li = document.createElement('li');
|
||
const label = item.type ? `${item.type}: ` : '';
|
||
li.textContent = `${label}${item.model || ''}`.trim();
|
||
printersConsumablesList.appendChild(li);
|
||
});
|
||
} else {
|
||
const li = document.createElement('li');
|
||
li.textContent = 'Нет привязанных материалов';
|
||
printersConsumablesList.appendChild(li);
|
||
}
|
||
if (printersId) printersId.value = data.id || '';
|
||
if (printersCabinetSelect) printersCabinetSelect.value = data.cabinet_id || '';
|
||
if (printersCabinetForm) {
|
||
printersCabinetForm.action = data.kind === 'device' ? '/devices/update_cabinet' : '/computers/update_cabinet';
|
||
}
|
||
setPrintersInfoVisible(true);
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
if (printersInv) {
|
||
let printersTimer = null;
|
||
printersInv.addEventListener('input', () => {
|
||
if (printersTimer) window.clearTimeout(printersTimer);
|
||
printersTimer = window.setTimeout(lookupPrintersInv, 160);
|
||
});
|
||
printersInv.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupPrintersInv();
|
||
}
|
||
});
|
||
}
|
||
|
||
function setComputersInfoVisible(visible) {
|
||
const action = visible ? 'remove' : 'add';
|
||
computersInfoInv.classList[action]('d-none');
|
||
computersInfoBrand.classList[action]('d-none');
|
||
computersInfoSerial.classList[action]('d-none');
|
||
computersInfoDate.classList[action]('d-none');
|
||
computersInfoCabinet.classList[action]('d-none');
|
||
computersCabinetForm.classList[action]('d-none');
|
||
computersSpecsWrap.classList[action]('d-none');
|
||
}
|
||
|
||
function formatGB(value) {
|
||
const raw = (value || '').toString().trim();
|
||
if (!raw) return '—';
|
||
const lower = raw.toLowerCase();
|
||
if (lower.includes('gb') || lower.includes('гб')) return raw;
|
||
const num = raw.match(/\\d+/);
|
||
if (num) return `${num[0]} ГБ`;
|
||
return raw;
|
||
}
|
||
|
||
async function lookupComputersInv() {
|
||
if (!computersInv) return;
|
||
const inv = (computersInv.value || '').trim();
|
||
if (!inv) {
|
||
setComputersInfoVisible(false);
|
||
computersInfoInv.textContent = '';
|
||
computersInfoBrand.textContent = '';
|
||
computersInfoSerial.textContent = '';
|
||
computersInfoDate.textContent = '';
|
||
computersInfoCabinet.textContent = '';
|
||
computersSpecCpu.textContent = '';
|
||
computersSpecGpu.textContent = '';
|
||
computersSpecRam.textContent = '';
|
||
computersSpecStorage.textContent = '';
|
||
computersSpecOs.textContent = '';
|
||
computersSpecMotherboard.textContent = '';
|
||
computersSpecMotherboard.classList.add('d-none');
|
||
if (computersId) computersId.value = '';
|
||
if (computersCabinetSelect) computersCabinetSelect.value = '';
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(inv)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
if (!data.found || data.kind !== 'computer') {
|
||
computersInfoInv.textContent = '';
|
||
computersInfoBrand.textContent = 'Компьютер/Ноутбук не найден';
|
||
computersInfoSerial.textContent = '';
|
||
computersInfoDate.textContent = '';
|
||
computersInfoCabinet.textContent = '';
|
||
computersSpecCpu.textContent = '';
|
||
computersSpecGpu.textContent = '';
|
||
computersSpecRam.textContent = '';
|
||
computersSpecStorage.textContent = '';
|
||
computersSpecOs.textContent = '';
|
||
computersSpecMotherboard.textContent = '';
|
||
computersSpecMotherboard.classList.add('d-none');
|
||
if (computersId) computersId.value = '';
|
||
if (computersCabinetSelect) computersCabinetSelect.value = '';
|
||
setComputersInfoVisible(true);
|
||
return;
|
||
}
|
||
const brandModel = [data.brand, data.model].filter(Boolean).join(' ');
|
||
computersInfoInv.textContent = `Инвентарный номер: ${data.inventory_number || inv}`;
|
||
computersInfoBrand.textContent = `Бренд/Модель: ${brandModel || ''}`.trim();
|
||
computersInfoSerial.textContent = `Серийный номер: ${data.serial_number || '—'}`;
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
const label = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
computersInfoDate.textContent = `Дата ввода в эксплуатацию: ${label}`;
|
||
} else {
|
||
computersInfoDate.textContent = 'Дата ввода в эксплуатацию: —';
|
||
}
|
||
computersInfoCabinet.textContent = `Расположение: ${data.cabinet_name || '—'}`;
|
||
if (computersId) computersId.value = data.id || '';
|
||
if (computersCabinetSelect) computersCabinetSelect.value = data.cabinet_id || '';
|
||
if (computersCabinetForm) computersCabinetForm.action = '/computers/update_cabinet';
|
||
|
||
const cpuLabel = [data.cpu_brand, data.cpu_model].filter(Boolean).join(' ');
|
||
if (data.type === 'pc') {
|
||
computersSpecMotherboard.textContent = `Материнская плата: ${data.motherboard || '—'}`;
|
||
computersSpecMotherboard.classList.remove('d-none');
|
||
if (computersSpecsList && computersSpecMotherboard) {
|
||
if (computersSpecMotherboard.parentElement !== computersSpecsList) {
|
||
computersSpecsList.insertBefore(computersSpecMotherboard, computersSpecsList.firstElementChild);
|
||
} else if (computersSpecsList.firstElementChild !== computersSpecMotherboard) {
|
||
computersSpecsList.insertBefore(computersSpecMotherboard, computersSpecsList.firstElementChild);
|
||
}
|
||
}
|
||
} else {
|
||
computersSpecMotherboard.textContent = '';
|
||
computersSpecMotherboard.classList.add('d-none');
|
||
}
|
||
computersSpecCpu.textContent = `CPU: ${cpuLabel || '—'}`;
|
||
computersSpecGpu.textContent = `GPU: ${data.gpu_model || '—'}`;
|
||
const memoryLabel = [formatGB(data.memory_size), data.memory_type].filter(v => v && v !== '—').join(' ');
|
||
computersSpecRam.textContent = `RAM: ${memoryLabel || '—'}`;
|
||
computersSpecStorage.textContent = `Накопитель: ${formatGB(data.storage_size)}`;
|
||
computersSpecOs.textContent = `ОС: ${data.os || '—'}`;
|
||
setComputersInfoVisible(true);
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
if (computersInv) {
|
||
let computersTimer = null;
|
||
computersInv.addEventListener('input', () => {
|
||
if (computersTimer) window.clearTimeout(computersTimer);
|
||
computersTimer = window.setTimeout(lookupComputersInv, 160);
|
||
});
|
||
computersInv.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupComputersInv();
|
||
}
|
||
});
|
||
}
|
||
|
||
function setProjectorsInfoVisible(visible) {
|
||
const action = visible ? 'remove' : 'add';
|
||
projectorsInfoInv.classList[action]('d-none');
|
||
projectorsInfoType.classList[action]('d-none');
|
||
projectorsInfoBrand.classList[action]('d-none');
|
||
projectorsInfoSerial.classList[action]('d-none');
|
||
projectorsInfoDate.classList[action]('d-none');
|
||
projectorsInfoCabinet.classList[action]('d-none');
|
||
projectorsCabinetForm.classList[action]('d-none');
|
||
projectorsKitWrap.classList[action]('d-none');
|
||
}
|
||
|
||
function formatBrandModel(brand, model) {
|
||
return [brand, model].filter(Boolean).join(' ');
|
||
}
|
||
|
||
function clearCabinetResults() {
|
||
if (cabinetInfoTitle) {
|
||
cabinetInfoTitle.textContent = '';
|
||
cabinetInfoTitle.classList.add('d-none');
|
||
}
|
||
if (cabinetDevicesWrap) cabinetDevicesWrap.classList.add('d-none');
|
||
if (cabinetDevicesList) cabinetDevicesList.innerHTML = '';
|
||
if (cabinetComputersWrap) cabinetComputersWrap.classList.add('d-none');
|
||
if (cabinetComputersList) cabinetComputersList.innerHTML = '';
|
||
if (cabinetProjectorsWrap) cabinetProjectorsWrap.classList.add('d-none');
|
||
if (cabinetProjectorsList) cabinetProjectorsList.innerHTML = '';
|
||
if (cabinetDocsWrap) cabinetDocsWrap.classList.add('d-none');
|
||
if (cabinetDocsList) cabinetDocsList.innerHTML = '';
|
||
if (cabinetEmptyNote) cabinetEmptyNote.classList.add('d-none');
|
||
}
|
||
|
||
function formatDate(value) {
|
||
if (!value) return '—';
|
||
const parsed = new Date(value);
|
||
if (Number.isNaN(parsed.getTime())) return value;
|
||
return parsed.toLocaleDateString('ru-RU');
|
||
}
|
||
|
||
function setSearchInfoVisible(visible) {
|
||
const action = visible ? 'remove' : 'add';
|
||
searchInfoInv.classList[action]('d-none');
|
||
searchInfoType.classList[action]('d-none');
|
||
searchInfoBrand.classList[action]('d-none');
|
||
searchInfoSerial.classList[action]('d-none');
|
||
searchInfoDate.classList[action]('d-none');
|
||
searchInfoCabinet.classList[action]('d-none');
|
||
}
|
||
|
||
function resetSearchExtras() {
|
||
if (searchConsumablesWrap) searchConsumablesWrap.classList.add('d-none');
|
||
if (searchConsumablesList) searchConsumablesList.innerHTML = '';
|
||
if (searchSpecsWrap) searchSpecsWrap.classList.add('d-none');
|
||
if (searchSpecCpu) searchSpecCpu.textContent = '';
|
||
if (searchSpecGpu) searchSpecGpu.textContent = '';
|
||
if (searchSpecRam) searchSpecRam.textContent = '';
|
||
if (searchSpecStorage) searchSpecStorage.textContent = '';
|
||
if (searchSpecOs) searchSpecOs.textContent = '';
|
||
if (searchSpecMotherboard) {
|
||
searchSpecMotherboard.textContent = '';
|
||
searchSpecMotherboard.classList.add('d-none');
|
||
}
|
||
if (searchKitWrap) searchKitWrap.classList.add('d-none');
|
||
if (searchKitProjectorTitle) searchKitProjectorTitle.textContent = '';
|
||
if (searchKitProjectorList) searchKitProjectorList.innerHTML = '';
|
||
if (searchKitBoardTitle) searchKitBoardTitle.textContent = '';
|
||
if (searchKitBoardList) searchKitBoardList.innerHTML = '';
|
||
if (searchKitComputerTitle) searchKitComputerTitle.textContent = '';
|
||
if (searchKitComputerList) searchKitComputerList.innerHTML = '';
|
||
}
|
||
|
||
async function lookupSearchInv() {
|
||
if (!searchInvInput) return;
|
||
const inv = (searchInvInput.value || '').trim();
|
||
if (!inv) {
|
||
setSearchInfoVisible(false);
|
||
if (searchInfoInv) searchInfoInv.textContent = '';
|
||
if (searchInfoType) searchInfoType.textContent = '';
|
||
if (searchInfoBrand) searchInfoBrand.textContent = '';
|
||
if (searchInfoSerial) searchInfoSerial.textContent = '';
|
||
if (searchInfoDate) searchInfoDate.textContent = '';
|
||
if (searchInfoCabinet) searchInfoCabinet.textContent = '';
|
||
resetSearchExtras();
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(inv)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
const allowedKinds = new Set(['device', 'computer', 'projector', 'board', 'document_camera']);
|
||
if (!data.found || !allowedKinds.has(data.kind)) {
|
||
if (searchInfoInv) searchInfoInv.textContent = 'Инвентарный номер не найден';
|
||
if (searchInfoType) searchInfoType.textContent = '';
|
||
if (searchInfoBrand) searchInfoBrand.textContent = '';
|
||
if (searchInfoSerial) searchInfoSerial.textContent = '';
|
||
if (searchInfoDate) searchInfoDate.textContent = '';
|
||
if (searchInfoCabinet) searchInfoCabinet.textContent = '';
|
||
resetSearchExtras();
|
||
searchInfoInv.classList.remove('d-none');
|
||
searchInfoType.classList.add('d-none');
|
||
searchInfoBrand.classList.add('d-none');
|
||
searchInfoSerial.classList.add('d-none');
|
||
searchInfoDate.classList.add('d-none');
|
||
searchInfoCabinet.classList.add('d-none');
|
||
return;
|
||
}
|
||
|
||
const kindLabelMap = {
|
||
device: 'Устройство',
|
||
computer: 'Компьютер/Ноутбук',
|
||
projector: 'Презентационное оборудование',
|
||
board: 'Доска',
|
||
document_camera: 'Документ-камера',
|
||
};
|
||
let typeLabel = kindLabelMap[data.kind] || 'Устройство';
|
||
if (data.kind === 'device') {
|
||
typeLabel = data.type_label || (data.type === 'printer' ? 'Принтер' : data.type === 'mfp' ? 'МФУ' : data.type || typeLabel);
|
||
} else if (data.kind === 'projector') {
|
||
typeLabel = data.kit_type_label || data.type_label || data.kit_type || typeLabel;
|
||
} else if (data.kind === 'board') {
|
||
typeLabel = data.type_label || typeLabel;
|
||
}
|
||
|
||
const modelValue = data.model || data.projector_model || data.board_model || '';
|
||
const brandModel = formatBrandModel(data.brand, modelValue);
|
||
const serialValue = data.serial_number || data.projector_serial || data.board_serial || '—';
|
||
|
||
searchInfoInv.textContent = `Инвентарный номер: ${data.inventory_number || inv}`;
|
||
searchInfoType.textContent = `Тип: ${typeLabel}`;
|
||
searchInfoBrand.textContent = `Бренд/Модель: ${brandModel || ''}`.trim();
|
||
searchInfoSerial.textContent = `Серийный номер: ${serialValue}`;
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
const label = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
searchInfoDate.textContent = `Дата ввода в эксплуатацию: ${label}`;
|
||
} else {
|
||
searchInfoDate.textContent = 'Дата ввода в эксплуатацию: —';
|
||
}
|
||
searchInfoCabinet.textContent = `Расположение: ${data.cabinet_name || '—'}`;
|
||
|
||
resetSearchExtras();
|
||
|
||
if (data.kind === 'device') {
|
||
const items = Array.isArray(data.consumables) ? data.consumables : [];
|
||
if (searchConsumablesWrap && searchConsumablesList) {
|
||
searchConsumablesWrap.classList.remove('d-none');
|
||
if (items.length) {
|
||
items.forEach((item) => {
|
||
const li = document.createElement('li');
|
||
const label = item.type ? `${item.type}: ` : '';
|
||
li.textContent = `${label}${item.model || ''}`.trim();
|
||
searchConsumablesList.appendChild(li);
|
||
});
|
||
} else {
|
||
const li = document.createElement('li');
|
||
li.textContent = 'Нет привязанных материалов';
|
||
searchConsumablesList.appendChild(li);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (data.kind === 'computer') {
|
||
const cpuLabel = [data.cpu_brand, data.cpu_model].filter(Boolean).join(' ');
|
||
if (searchSpecMotherboard) {
|
||
if (data.type === 'pc') {
|
||
searchSpecMotherboard.textContent = `Материнская плата: ${data.motherboard || '—'}`;
|
||
searchSpecMotherboard.classList.remove('d-none');
|
||
} else {
|
||
searchSpecMotherboard.textContent = '';
|
||
searchSpecMotherboard.classList.add('d-none');
|
||
}
|
||
}
|
||
if (searchSpecCpu) searchSpecCpu.textContent = `CPU: ${cpuLabel || '—'}`;
|
||
if (searchSpecGpu) searchSpecGpu.textContent = `GPU: ${data.gpu_model || '—'}`;
|
||
const memoryLabel = [formatGB(data.memory_size), data.memory_type].filter(v => v && v !== '—').join(' ');
|
||
if (searchSpecRam) searchSpecRam.textContent = `RAM: ${memoryLabel || '—'}`;
|
||
if (searchSpecStorage) searchSpecStorage.textContent = `Накопитель: ${formatGB(data.storage_size)}`;
|
||
if (searchSpecOs) searchSpecOs.textContent = `ОС: ${data.os || '—'}`;
|
||
if (searchSpecsWrap) searchSpecsWrap.classList.remove('d-none');
|
||
}
|
||
|
||
const showKit = data.kind === 'projector'
|
||
&& data.kit_type === 'kit'
|
||
&& data.inventory_number === inv;
|
||
if (showKit && searchKitWrap) {
|
||
const projModel = formatBrandModel(data.projector_brand || data.brand, data.projector_model);
|
||
const boardModel = formatBrandModel(data.board_brand || data.brand, data.board_model);
|
||
const compModel = formatBrandModel(data.computer_brand, data.computer_model);
|
||
searchKitWrap.classList.remove('d-none');
|
||
if (searchKitProjectorTitle) searchKitProjectorTitle.textContent = 'Проектор:';
|
||
if (searchKitProjectorList) {
|
||
searchKitProjectorList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.projector_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${projModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.projector_serial || '—'}</li>
|
||
`;
|
||
}
|
||
if (searchKitBoardTitle) searchKitBoardTitle.textContent = 'Доска:';
|
||
if (searchKitBoardList) {
|
||
searchKitBoardList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.board_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${boardModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.board_serial || '—'}</li>
|
||
`;
|
||
}
|
||
if (searchKitComputerTitle) searchKitComputerTitle.textContent = 'Компьютер:';
|
||
if (searchKitComputerList) {
|
||
searchKitComputerList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.computer_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${compModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.computer_serial || '—'}</li>
|
||
`;
|
||
}
|
||
}
|
||
|
||
setSearchInfoVisible(true);
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
function appendListItem(list, lines) {
|
||
const li = document.createElement('li');
|
||
li.className = 'home-modal__status';
|
||
li.innerHTML = lines.map(line => `<div>${line}</div>`).join('');
|
||
list.appendChild(li);
|
||
}
|
||
|
||
async function lookupCabinetInfo() {
|
||
if (!searchCabinetViewSelect) return;
|
||
const cabId = (searchCabinetViewSelect.value || '').trim();
|
||
if (!cabId) {
|
||
clearCabinetResults();
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/cabinet_info?cabinet_id=${encodeURIComponent(cabId)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
clearCabinetResults();
|
||
|
||
if (cabinetInfoTitle) {
|
||
const title = data.cabinet_name ? `Кабинет: ${data.cabinet_name}` : 'Кабинет';
|
||
cabinetInfoTitle.textContent = title;
|
||
cabinetInfoTitle.classList.remove('d-none');
|
||
}
|
||
|
||
const devices = Array.isArray(data.devices) ? data.devices : [];
|
||
if (devices.length && cabinetDevicesWrap && cabinetDevicesList) {
|
||
devices.forEach((item) => {
|
||
const brandModel = formatBrandModel(item.brand, item.model);
|
||
const typeLabel = item.type_label || item.type || 'Устройство';
|
||
const consumables = Array.isArray(item.consumables) ? item.consumables : [];
|
||
const consLabel = consumables.length
|
||
? consumables.map(c => (c.type ? `${c.type}: ${c.model || ''}` : c.model || '')).filter(Boolean).join('; ')
|
||
: 'Нет привязанных материалов';
|
||
appendListItem(cabinetDevicesList, [
|
||
`Инв. №: ${item.inventory_number || '—'}`,
|
||
`Тип: ${typeLabel}`,
|
||
`Бренд/Модель: ${brandModel || '—'}`,
|
||
`Серийный №: ${item.serial_number || '—'}`,
|
||
`Дата ввода: ${formatDate(item.date_in_operation)}`,
|
||
`Расходные материалы: ${consLabel}`,
|
||
]);
|
||
});
|
||
cabinetDevicesWrap.classList.remove('d-none');
|
||
}
|
||
|
||
const computers = Array.isArray(data.computers) ? data.computers : [];
|
||
if (computers.length && cabinetComputersWrap && cabinetComputersList) {
|
||
computers.forEach((item) => {
|
||
const brandModel = formatBrandModel(item.brand, item.model);
|
||
const cpuLabel = [item.cpu_brand, item.cpu_model].filter(Boolean).join(' ');
|
||
const memoryLabel = [formatGB(item.memory_size), item.memory_type].filter(v => v && v !== '—').join(' ');
|
||
const lines = [
|
||
`Инв. №: ${item.inventory_number || '—'}`,
|
||
`Тип: ${item.type_label || item.type || '—'}`,
|
||
`Бренд/Модель: ${brandModel || '—'}`,
|
||
`Серийный №: ${item.serial_number || '—'}`,
|
||
`CPU: ${cpuLabel || '—'}`,
|
||
`GPU: ${item.gpu_model || '—'}`,
|
||
`RAM: ${memoryLabel || '—'}`,
|
||
`Накопитель: ${formatGB(item.storage_size)}`,
|
||
`ОС: ${item.os || '—'}`,
|
||
];
|
||
if (item.type === 'pc') {
|
||
lines.push(`Материнская плата: ${item.motherboard || '—'}`);
|
||
}
|
||
appendListItem(cabinetComputersList, lines);
|
||
});
|
||
cabinetComputersWrap.classList.remove('d-none');
|
||
}
|
||
|
||
const projectors = Array.isArray(data.projectors) ? data.projectors : [];
|
||
if (projectors.length && cabinetProjectorsWrap && cabinetProjectorsList) {
|
||
projectors.forEach((item) => {
|
||
const typeLabel = item.kit_type_label || item.kit_type || 'Проектор';
|
||
const projModel = formatBrandModel(item.projector_brand || item.brand, item.projector_model);
|
||
const boardModel = formatBrandModel(item.board_brand || item.brand, item.board_model);
|
||
const compModel = formatBrandModel(item.computer_brand, item.computer_model);
|
||
const lines = [
|
||
`Инв. №: ${item.inventory_number || '—'}`,
|
||
`Тип: ${typeLabel}`,
|
||
`Проектор: ${projModel || '—'}`,
|
||
`Серийный № проектора: ${item.projector_serial || '—'}`,
|
||
];
|
||
if (item.projector_inventory_number) {
|
||
lines.push(`Инв. № проектора: ${item.projector_inventory_number}`);
|
||
}
|
||
if (item.board_model || item.board_serial || item.board_inventory_number) {
|
||
lines.push(`Доска: ${boardModel || '—'}`);
|
||
lines.push(`Серийный № доски: ${item.board_serial || '—'}`);
|
||
if (item.board_inventory_number) {
|
||
lines.push(`Инв. № доски: ${item.board_inventory_number}`);
|
||
}
|
||
}
|
||
if (item.computer_inventory_number || compModel) {
|
||
lines.push(`Компьютер: ${compModel || '—'}`);
|
||
if (item.computer_inventory_number) {
|
||
lines.push(`Инв. № компьютера: ${item.computer_inventory_number}`);
|
||
}
|
||
}
|
||
appendListItem(cabinetProjectorsList, lines);
|
||
});
|
||
cabinetProjectorsWrap.classList.remove('d-none');
|
||
}
|
||
|
||
const docs = Array.isArray(data.document_cameras) ? data.document_cameras : [];
|
||
if (docs.length && cabinetDocsWrap && cabinetDocsList) {
|
||
docs.forEach((item) => {
|
||
const brandModel = formatBrandModel(item.brand, item.model);
|
||
appendListItem(cabinetDocsList, [
|
||
`Инв. №: ${item.inventory_number || '—'}`,
|
||
`Бренд/Модель: ${brandModel || '—'}`,
|
||
`Серийный №: ${item.serial_number || '—'}`,
|
||
`Дата ввода: ${formatDate(item.date_in_operation)}`,
|
||
]);
|
||
});
|
||
cabinetDocsWrap.classList.remove('d-none');
|
||
}
|
||
|
||
const totalItems = devices.length + computers.length + projectors.length + docs.length;
|
||
if (!totalItems && cabinetEmptyNote) {
|
||
cabinetEmptyNote.classList.remove('d-none');
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
async function lookupProjectorsInv() {
|
||
if (!projectorsInv) return;
|
||
const inv = (projectorsInv.value || '').trim();
|
||
if (!inv) {
|
||
setProjectorsInfoVisible(false);
|
||
projectorsInfoInv.textContent = '';
|
||
projectorsInfoType.textContent = '';
|
||
projectorsInfoBrand.textContent = '';
|
||
projectorsInfoSerial.textContent = '';
|
||
projectorsInfoDate.textContent = '';
|
||
projectorsInfoCabinet.textContent = '';
|
||
projectorsInfoInv.classList.add('d-none');
|
||
kitProjectorTitle.textContent = '';
|
||
kitProjectorList.innerHTML = '';
|
||
kitBoardTitle.textContent = '';
|
||
kitBoardList.innerHTML = '';
|
||
kitComputerTitle.textContent = '';
|
||
kitComputerList.innerHTML = '';
|
||
if (projectorsId) projectorsId.value = '';
|
||
if (projectorsCabinetSelect) projectorsCabinetSelect.value = '';
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(inv)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
const isProjectorKind = data.kind === 'projector' || data.kind === 'board';
|
||
if (!data.found || !isProjectorKind) {
|
||
projectorsInfoInv.textContent = 'Инвентарный номер не найден';
|
||
projectorsInfoInv.classList.remove('d-none');
|
||
projectorsInfoType.textContent = '';
|
||
projectorsInfoBrand.textContent = '';
|
||
projectorsInfoSerial.textContent = '';
|
||
projectorsInfoDate.textContent = '';
|
||
projectorsInfoCabinet.textContent = '';
|
||
kitProjectorTitle.textContent = '';
|
||
kitProjectorList.innerHTML = '';
|
||
kitBoardTitle.textContent = '';
|
||
kitBoardList.innerHTML = '';
|
||
kitComputerTitle.textContent = '';
|
||
kitComputerList.innerHTML = '';
|
||
if (projectorsId) projectorsId.value = '';
|
||
if (projectorsCabinetSelect) projectorsCabinetSelect.value = '';
|
||
setProjectorsInfoVisible(true);
|
||
return;
|
||
}
|
||
|
||
projectorsInfoInv.textContent = `Инвентарный номер: ${data.inventory_number || inv}`;
|
||
projectorsInfoInv.classList.remove('d-none');
|
||
const typeLabel = data.kind === 'board'
|
||
? (data.type_label || 'Доска')
|
||
: (data.kit_type_label || data.type_label || data.kit_type || 'Проектор');
|
||
projectorsInfoType.textContent = `Тип: ${typeLabel}`;
|
||
|
||
const brandModel = formatBrandModel(data.brand, data.model || data.projector_model || data.board_model);
|
||
projectorsInfoBrand.textContent = `Бренд/Модель: ${brandModel || ''}`.trim();
|
||
projectorsInfoSerial.textContent = `Серийный номер: ${data.serial_number || data.projector_serial || data.board_serial || '—'}`;
|
||
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
const label = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
projectorsInfoDate.textContent = `Дата ввода в эксплуатацию: ${label}`;
|
||
} else {
|
||
projectorsInfoDate.textContent = 'Дата ввода в эксплуатацию: —';
|
||
}
|
||
|
||
projectorsInfoCabinet.textContent = `Кабинет: ${data.cabinet_name || '—'}`;
|
||
if (projectorsId) projectorsId.value = data.id || '';
|
||
if (projectorsCabinetSelect) projectorsCabinetSelect.value = data.cabinet_id || '';
|
||
if (projectorsCabinetForm) projectorsCabinetForm.action = '/projectors/update_cabinet';
|
||
|
||
const showKit = data.kind === 'projector'
|
||
&& data.kit_type === 'kit'
|
||
&& data.inventory_number === inv;
|
||
if (showKit) {
|
||
projectorsKitWrap.classList.remove('d-none');
|
||
const projModel = formatBrandModel(data.projector_brand || data.brand, data.projector_model);
|
||
const boardModel = formatBrandModel(data.board_brand || data.brand, data.board_model);
|
||
const compModel = formatBrandModel(data.computer_brand, data.computer_model);
|
||
kitProjectorTitle.textContent = 'Проектор:';
|
||
kitProjectorList.innerHTML = '';
|
||
kitProjectorList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.projector_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${projModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.projector_serial || '—'}</li>
|
||
`;
|
||
kitBoardTitle.textContent = 'Доска:';
|
||
kitBoardList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.board_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${boardModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.board_serial || '—'}</li>
|
||
`;
|
||
kitComputerTitle.textContent = 'Компьютер:';
|
||
kitComputerList.innerHTML = `
|
||
<li class="home-modal__status">Инвентарный номер: ${data.computer_inventory_number || '—'}</li>
|
||
<li class="home-modal__status">Бренд/Модель: ${compModel || '—'}</li>
|
||
<li class="home-modal__status">Серийный номер: ${data.computer_serial || '—'}</li>
|
||
`;
|
||
} else {
|
||
projectorsKitWrap.classList.add('d-none');
|
||
kitProjectorTitle.textContent = '';
|
||
kitProjectorList.innerHTML = '';
|
||
kitBoardTitle.textContent = '';
|
||
kitBoardList.innerHTML = '';
|
||
kitComputerTitle.textContent = '';
|
||
kitComputerList.innerHTML = '';
|
||
}
|
||
|
||
setProjectorsInfoVisible(true);
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
if (projectorsInv) {
|
||
let projectorsTimer = null;
|
||
projectorsInv.addEventListener('input', () => {
|
||
if (projectorsTimer) window.clearTimeout(projectorsTimer);
|
||
projectorsTimer = window.setTimeout(lookupProjectorsInv, 160);
|
||
});
|
||
projectorsInv.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupProjectorsInv();
|
||
}
|
||
});
|
||
}
|
||
|
||
function setDocCameraInfoVisible(visible) {
|
||
const action = visible ? 'remove' : 'add';
|
||
docCameraInfoInv.classList[action]('d-none');
|
||
docCameraInfoBrand.classList[action]('d-none');
|
||
docCameraInfoSerial.classList[action]('d-none');
|
||
docCameraInfoDate.classList[action]('d-none');
|
||
docCameraInfoCabinet.classList[action]('d-none');
|
||
docCameraCabinetForm.classList[action]('d-none');
|
||
}
|
||
|
||
async function lookupDocCameraInv() {
|
||
if (!docCameraInv) return;
|
||
const inv = (docCameraInv.value || '').trim();
|
||
if (!inv) {
|
||
setDocCameraInfoVisible(false);
|
||
docCameraInfoInv.textContent = '';
|
||
docCameraInfoBrand.textContent = '';
|
||
docCameraInfoSerial.textContent = '';
|
||
docCameraInfoDate.textContent = '';
|
||
docCameraInfoCabinet.textContent = '';
|
||
if (docCameraId) docCameraId.value = '';
|
||
if (docCameraCabinetSelect) docCameraCabinetSelect.value = '';
|
||
return;
|
||
}
|
||
try {
|
||
const res = await fetch(`/api/barcode?barcode=${encodeURIComponent(inv)}`);
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
if (!data.found || data.kind !== 'document_camera') {
|
||
docCameraInfoInv.textContent = 'Документ-камера не найдена';
|
||
docCameraInfoBrand.textContent = '';
|
||
docCameraInfoSerial.textContent = '';
|
||
docCameraInfoDate.textContent = '';
|
||
docCameraInfoCabinet.textContent = '';
|
||
if (docCameraId) docCameraId.value = '';
|
||
if (docCameraCabinetSelect) docCameraCabinetSelect.value = '';
|
||
setDocCameraInfoVisible(true);
|
||
return;
|
||
}
|
||
const brandModel = [data.brand, data.model].filter(Boolean).join(' ');
|
||
docCameraInfoInv.textContent = `Инвентарный номер: ${data.inventory_number || inv}`;
|
||
docCameraInfoBrand.textContent = `Бренд/Модель: ${brandModel || ''}`.trim();
|
||
docCameraInfoSerial.textContent = `Серийный номер: ${data.serial_number || '—'}`;
|
||
if (data.date_in_operation) {
|
||
const parsed = new Date(data.date_in_operation);
|
||
const label = Number.isNaN(parsed.getTime())
|
||
? data.date_in_operation
|
||
: parsed.toLocaleDateString('ru-RU');
|
||
docCameraInfoDate.textContent = `Дата ввода в эксплуатацию: ${label}`;
|
||
} else {
|
||
docCameraInfoDate.textContent = 'Дата ввода в эксплуатацию: —';
|
||
}
|
||
docCameraInfoCabinet.textContent = `Кабинет: ${data.cabinet_name || '—'}`;
|
||
if (docCameraId) docCameraId.value = data.id || '';
|
||
if (docCameraCabinetSelect) docCameraCabinetSelect.value = data.cabinet_id || '';
|
||
if (docCameraCabinetForm) docCameraCabinetForm.action = '/document_cameras/update_cabinet';
|
||
setDocCameraInfoVisible(true);
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
if (docCameraInv) {
|
||
let docTimer = null;
|
||
docCameraInv.addEventListener('input', () => {
|
||
if (docTimer) window.clearTimeout(docTimer);
|
||
docTimer = window.setTimeout(lookupDocCameraInv, 160);
|
||
});
|
||
docCameraInv.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupDocCameraInv();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (searchCabinetViewSelect) {
|
||
searchCabinetViewSelect.addEventListener('change', lookupCabinetInfo);
|
||
}
|
||
if (searchInvInput) {
|
||
let searchTimer = null;
|
||
searchInvInput.addEventListener('input', () => {
|
||
if (searchTimer) window.clearTimeout(searchTimer);
|
||
searchTimer = window.setTimeout(lookupSearchInv, 160);
|
||
});
|
||
searchInvInput.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
lookupSearchInv();
|
||
}
|
||
});
|
||
}
|
||
|
||
function syncBrand(selectEl, customEl, valueEl) {
|
||
if (!selectEl || !customEl || !valueEl) return;
|
||
const sel = selectEl.value;
|
||
if (sel === '__custom__') {
|
||
customEl.classList.remove('d-none');
|
||
customEl.disabled = false;
|
||
valueEl.value = customEl.value || '';
|
||
} else {
|
||
customEl.classList.add('d-none');
|
||
customEl.disabled = true;
|
||
customEl.value = '';
|
||
valueEl.value = sel;
|
||
}
|
||
}
|
||
|
||
function attachBrandSync(selectEl, customEl, valueEl) {
|
||
if (!selectEl || !customEl || !valueEl) return;
|
||
selectEl.addEventListener('change', () => syncBrand(selectEl, customEl, valueEl));
|
||
customEl.addEventListener('input', () => syncBrand(selectEl, customEl, valueEl));
|
||
syncBrand(selectEl, customEl, valueEl);
|
||
}
|
||
|
||
attachBrandSync(homeDeviceBrandSelect, homeDeviceBrandCustom, homeDeviceBrandValue);
|
||
attachBrandSync(homeComputerBrandSelect, homeComputerBrandCustom, homeComputerBrandValue);
|
||
attachBrandSync(homeProjectorBrandSelect, homeProjectorBrandCustom, homeProjectorBrandValue);
|
||
attachBrandSync(homeDocBrandSelect, homeDocBrandCustom, homeDocBrandValue);
|
||
|
||
if (homeProjectorComputerSelect && homeProjectorComputerInv) {
|
||
const syncFromSelect = () => {
|
||
const opt = homeProjectorComputerSelect.options[homeProjectorComputerSelect.selectedIndex];
|
||
const inv = opt ? (opt.getAttribute('data-inv') || '') : '';
|
||
homeProjectorComputerInv.value = inv;
|
||
};
|
||
const syncFromInv = () => {
|
||
const inv = (homeProjectorComputerInv.value || '').trim();
|
||
if (!inv) return;
|
||
const opts = Array.from(homeProjectorComputerSelect.options);
|
||
const match = opts.find(o => (o.getAttribute('data-inv') || '') === inv);
|
||
if (match) {
|
||
homeProjectorComputerSelect.value = match.value;
|
||
}
|
||
};
|
||
homeProjectorComputerSelect.addEventListener('change', syncFromSelect);
|
||
homeProjectorComputerInv.addEventListener('input', syncFromInv);
|
||
syncFromSelect();
|
||
}
|
||
|
||
if (addDeviceType && addDeviceForms.length) {
|
||
const showForm = (value) => {
|
||
addDeviceForms.forEach((form) => {
|
||
form.classList.toggle('d-none', form.getAttribute('data-add-type') !== value);
|
||
});
|
||
};
|
||
addDeviceType.addEventListener('change', () => showForm(addDeviceType.value));
|
||
}
|
||
|
||
if (homeComputerSpecsToggle && homeComputerSpecs) {
|
||
homeComputerSpecsToggle.addEventListener('click', () => {
|
||
homeComputerSpecs.classList.toggle('d-none');
|
||
});
|
||
}
|
||
|
||
const homeProjectorKitType = document.getElementById('homeProjectorKitType');
|
||
if (homeProjectorKitType) {
|
||
const projectorForm = homeProjectorKitType.closest('form');
|
||
const brandField = document.getElementById('homeProjectorBrandField');
|
||
const brandRowKit = document.getElementById('homeProjectorBrandRowKit');
|
||
const brandRowMain = document.getElementById('homeProjectorBrandRowMain');
|
||
const projectorModelLabelEl = document.getElementById('homeProjectorModelLabel');
|
||
const projectorSerialLabelEl = document.getElementById('homeProjectorSerialLabel');
|
||
const applyKitType = () => {
|
||
const val = homeProjectorKitType.value;
|
||
if (brandField && brandRowKit && brandRowMain) {
|
||
if (val === 'kit') {
|
||
if (brandField.parentElement !== brandRowKit) {
|
||
brandRowKit.appendChild(brandField);
|
||
}
|
||
} else {
|
||
if (brandField.parentElement !== brandRowMain) {
|
||
brandRowMain.insertBefore(brandField, brandRowMain.firstChild);
|
||
}
|
||
}
|
||
}
|
||
const fields = projectorForm.querySelectorAll('[data-kit-types]');
|
||
fields.forEach((field) => {
|
||
const types = (field.dataset.kitTypes || '').split(',').map(t => t.trim()).filter(Boolean);
|
||
const show = !!val && (!types.length || types.includes(val));
|
||
field.classList.toggle('d-none', !show);
|
||
field.querySelectorAll('input, select, textarea, button').forEach((el) => {
|
||
el.disabled = !show;
|
||
});
|
||
});
|
||
if (projectorModelLabelEl && projectorSerialLabelEl) {
|
||
if (val === 'display') {
|
||
projectorModelLabelEl.textContent = 'Модель интерактивного экрана';
|
||
projectorSerialLabelEl.textContent = 'Серийный № интерактивного экрана';
|
||
} else if (val === 'tv') {
|
||
projectorModelLabelEl.textContent = 'Модель телевизора';
|
||
projectorSerialLabelEl.textContent = 'Серийный № телевизора';
|
||
} else if (val === 'board') {
|
||
projectorModelLabelEl.textContent = 'Модель доски';
|
||
projectorSerialLabelEl.textContent = 'Серийный № доски';
|
||
} else {
|
||
projectorModelLabelEl.textContent = 'Модель проектора';
|
||
projectorSerialLabelEl.textContent = 'Серийный № проектора';
|
||
}
|
||
}
|
||
};
|
||
homeProjectorKitType.addEventListener('change', applyKitType);
|
||
applyKitType();
|
||
}
|
||
|
||
const factEl = document.getElementById('homeFactText');
|
||
if (factEl) {
|
||
const facts = [
|
||
'Первый жёсткий диск IBM 350 в 1956 году вмещал всего 5 МБ и весил почти тонну.',
|
||
'Лазерная печать появилась в 1970-х: технология Xerox стала основой современных лазерных принтеров.',
|
||
'Термин «баг» в программировании закрепился после реального мотылька в реле компьютера Mark II.',
|
||
'В первых принтерах использовались ударные механизмы, похожие на печатные машинки.',
|
||
'Первые ЭВМ занимали целые комнаты, а сегодня мощности смартфона хватает для задач того уровня.',
|
||
'Матричные принтеры до сих пор ценят за возможность печати на копиях и самокопирующихся бланках.',
|
||
'Первая коммерческая мышь была деревянной и имела два колеса.',
|
||
'Твердотельные накопители (SSD) работают без механики, поэтому быстрее и тише HDD.',
|
||
'Цветная печать требует точного совмещения слоёв, поэтому принтеры регулярно выполняют калибровку.',
|
||
'Первые графические интерфейсы вдохновили современные окна и меню, которые мы используем каждый день.',
|
||
'Скорость печати часто измеряют в страницах в минуту (PPM), но качество зависит и от режима печати.',
|
||
'Картриджи с тонером используют порошок, который закрепляется на бумаге при нагреве.',
|
||
'Первая версия USB появилась в 1996 году и резко упростила подключение периферии.',
|
||
'Оптические сенсоры мыши вытеснили шариковые благодаря большей точности и надёжности.',
|
||
'Лазерный принтер формирует изображение электростатикой на барабане и переносит тонер на бумагу.',
|
||
'Сканеры CIS тоньше и дешевле CCD, но обычно хуже передают глубину и цвет.',
|
||
'Чем выше DPI, тем больше деталей принтер может воспроизвести, но это не всегда улучшает текст.',
|
||
'Жизненный цикл картриджа зависит от покрытия страницы: 5% заполнение — стандарт отрасли.',
|
||
'Термо-принтеры печатают без чернил — изображение появляется на термобумаге.',
|
||
'Жёсткие диски HDD хранят данные на вращающихся пластинах с магнитным покрытием.',
|
||
'Сетевые принтеры часто используют протоколы IPP и LPR/LPD для печати по сети.',
|
||
'Первая коммерческая ЭВМ UNIVAC I поставлялась в 1951 году и весила более 7 тонн.',
|
||
'Память DDR повышает скорость, передавая данные дважды за такт.',
|
||
'Современные МФУ объединяют печать, сканирование и копирование в одном устройстве.',
|
||
'Видеопроцессор (GPU) ускоряет работу графики и параллельных вычислений.',
|
||
'Для увеличения ресурса лазерного картриджа важно использовать качественную бумагу.',
|
||
'Шум от вентиляторов — один из основных источников звука в системном блоке.',
|
||
'Печать в черновом режиме может снизить расход тонера.',
|
||
'USB-C может передавать данные, видео и питание через один кабель.',
|
||
'В первых ноутбуках аккумуляторы обеспечивали работу всего на 1–2 часа.',
|
||
'Механические клавиатуры ценят за ресурс и чёткую тактильную отдачу.',
|
||
];
|
||
let idx = Math.floor(Math.random() * facts.length);
|
||
const showFact = () => {
|
||
factEl.textContent = facts[idx % facts.length];
|
||
idx += 1;
|
||
};
|
||
showFact();
|
||
window.setInterval(showFact, 5 * 60 * 1000);
|
||
}
|
||
|
||
function noteRequest(url, payload) {
|
||
return fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload || {}),
|
||
});
|
||
}
|
||
|
||
function collectNotePayload(noteEl) {
|
||
const textarea = noteEl.querySelector('.home-user-note__text');
|
||
return {
|
||
text: textarea ? textarea.value : '',
|
||
x: Math.round(parseFloat(noteEl.style.left) || 0),
|
||
y: Math.round(parseFloat(noteEl.style.top) || 0),
|
||
pinned: noteEl.classList.contains('is-pinned'),
|
||
z: parseInt(noteEl.style.zIndex || '1', 10) || 1,
|
||
};
|
||
}
|
||
|
||
function ensureNoteInViewport(noteEl) {
|
||
const width = noteEl.offsetWidth || 240;
|
||
const height = noteEl.offsetHeight || 170;
|
||
const maxLeft = Math.max(0, window.innerWidth - width);
|
||
const maxTop = Math.max(44, window.innerHeight - height);
|
||
const left = parseFloat(noteEl.style.left) || 8;
|
||
const top = parseFloat(noteEl.style.top) || 90;
|
||
noteEl.style.left = `${Math.max(0, Math.min(maxLeft, left))}px`;
|
||
noteEl.style.top = `${Math.max(44, Math.min(maxTop, top))}px`;
|
||
}
|
||
|
||
function applyNotePinnedState(noteEl, pinned) {
|
||
const pinBtn = noteEl.querySelector('.home-user-note__pin');
|
||
noteEl.classList.toggle('is-pinned', !!pinned);
|
||
if (pinBtn) {
|
||
pinBtn.classList.toggle('is-active', !!pinned);
|
||
pinBtn.setAttribute('aria-pressed', pinned ? 'true' : 'false');
|
||
pinBtn.title = pinned ? '\u041E\u0442\u043A\u0440\u0435\u043F\u0438\u0442\u044C' : '\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C';
|
||
}
|
||
}
|
||
|
||
function refreshDeleteButtonsState() {
|
||
if (!homeNotesLayer) return;
|
||
const notes = Array.from(homeNotesLayer.querySelectorAll('.home-user-note'));
|
||
const oneNoteLeft = notes.length <= 1;
|
||
notes.forEach((noteEl) => {
|
||
const delBtn = noteEl.querySelector('.home-user-note__delete');
|
||
if (!delBtn) return;
|
||
delBtn.disabled = oneNoteLeft;
|
||
delBtn.title = oneNoteLeft ? '\u041D\u0435\u043B\u044C\u0437\u044F \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u0443\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443' : '\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0443';
|
||
});
|
||
}
|
||
|
||
function bringNoteToFront(noteEl) {
|
||
homeNotesMaxZ += 1;
|
||
noteEl.style.zIndex = String(homeNotesMaxZ);
|
||
}
|
||
|
||
async function createNoteAndMount(focusText) {
|
||
try {
|
||
const res = await noteRequest('/api/home_notes/create', {});
|
||
if (!res.ok) return null;
|
||
const data = await res.json();
|
||
if (!data || !data.ok || !data.note) return null;
|
||
const noteEl = mountNote(data.note);
|
||
if (noteEl) {
|
||
bringNoteToFront(noteEl);
|
||
refreshDeleteButtonsState();
|
||
if (focusText) {
|
||
const textarea = noteEl.querySelector('.home-user-note__text');
|
||
if (textarea) textarea.focus();
|
||
}
|
||
}
|
||
return noteEl;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
const noteSaveTimers = new Map();
|
||
function scheduleNoteSave(noteEl, delayMs) {
|
||
const id = noteEl.getAttribute('data-note-id');
|
||
if (!id) return;
|
||
const prev = noteSaveTimers.get(id);
|
||
if (prev) window.clearTimeout(prev);
|
||
const timerId = window.setTimeout(async () => {
|
||
noteSaveTimers.delete(id);
|
||
const payload = collectNotePayload(noteEl);
|
||
try {
|
||
await noteRequest(`/api/home_notes/${encodeURIComponent(id)}/update`, payload);
|
||
} catch (e) {
|
||
// ignore network errors for now
|
||
}
|
||
}, delayMs);
|
||
noteSaveTimers.set(id, timerId);
|
||
}
|
||
|
||
function mountNote(note) {
|
||
if (!homeNotesLayer) return null;
|
||
const noteEl = document.createElement('div');
|
||
noteEl.className = 'home-user-note';
|
||
noteEl.setAttribute('data-note-id', String(note.id));
|
||
noteEl.style.left = `${Number(note.x) || 8}px`;
|
||
noteEl.style.top = `${Number(note.y) || 90}px`;
|
||
noteEl.style.zIndex = String(Number(note.z) || 1);
|
||
homeNotesMaxZ = Math.max(homeNotesMaxZ, Number(note.z) || 1);
|
||
|
||
const head = document.createElement('div');
|
||
head.className = 'home-user-note__head';
|
||
const actions = document.createElement('div');
|
||
actions.className = 'home-user-note__head-actions';
|
||
|
||
const addBtn = document.createElement('button');
|
||
addBtn.type = 'button';
|
||
addBtn.className = 'home-user-note__add';
|
||
addBtn.textContent = '+';
|
||
addBtn.setAttribute('aria-label', '\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0443');
|
||
addBtn.title = '\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0443';
|
||
|
||
const pinBtn = document.createElement('button');
|
||
pinBtn.type = 'button';
|
||
pinBtn.className = 'home-user-note__pin';
|
||
pinBtn.textContent = '\uD83D\uDCCC';
|
||
pinBtn.setAttribute('aria-label', '\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0443');
|
||
pinBtn.title = '\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C';
|
||
|
||
actions.appendChild(addBtn);
|
||
actions.appendChild(pinBtn);
|
||
head.appendChild(actions);
|
||
|
||
const textarea = document.createElement('textarea');
|
||
textarea.className = 'home-user-note__text';
|
||
textarea.value = note.text || '';
|
||
textarea.placeholder = '\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442...';
|
||
|
||
const deleteBtn = document.createElement('button');
|
||
deleteBtn.type = 'button';
|
||
deleteBtn.className = 'home-user-note__delete';
|
||
deleteBtn.textContent = '\u00D7';
|
||
deleteBtn.setAttribute('aria-label', '\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0443');
|
||
|
||
noteEl.appendChild(head);
|
||
noteEl.appendChild(textarea);
|
||
noteEl.appendChild(deleteBtn);
|
||
applyNotePinnedState(noteEl, !!note.pinned);
|
||
homeNotesLayer.appendChild(noteEl);
|
||
ensureNoteInViewport(noteEl);
|
||
|
||
noteEl.addEventListener('mousedown', () => {
|
||
bringNoteToFront(noteEl);
|
||
scheduleNoteSave(noteEl, 120);
|
||
});
|
||
|
||
addBtn.addEventListener('click', async () => {
|
||
await createNoteAndMount(true);
|
||
});
|
||
|
||
deleteBtn.addEventListener('click', async () => {
|
||
if (deleteBtn.disabled) return;
|
||
const id = noteEl.getAttribute('data-note-id');
|
||
if (!id) return;
|
||
try {
|
||
const res = await noteRequest(`/api/home_notes/${encodeURIComponent(id)}/delete`, {});
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
if (!data || !data.ok) return;
|
||
noteEl.remove();
|
||
refreshDeleteButtonsState();
|
||
} catch (e) {
|
||
// ignore delete errors
|
||
}
|
||
});
|
||
|
||
pinBtn.addEventListener('click', () => {
|
||
const nextPinned = !noteEl.classList.contains('is-pinned');
|
||
applyNotePinnedState(noteEl, nextPinned);
|
||
scheduleNoteSave(noteEl, 80);
|
||
});
|
||
|
||
textarea.addEventListener('input', () => scheduleNoteSave(noteEl, 350));
|
||
textarea.addEventListener('blur', () => scheduleNoteSave(noteEl, 40));
|
||
|
||
let isDragging = false;
|
||
let dragStartX = 0;
|
||
let dragStartY = 0;
|
||
let startLeft = 0;
|
||
let startTop = 0;
|
||
let pointerId = null;
|
||
|
||
const onPointerMove = (e) => {
|
||
if (!isDragging) return;
|
||
const dx = e.clientX - dragStartX;
|
||
const dy = e.clientY - dragStartY;
|
||
noteEl.style.left = `${startLeft + dx}px`;
|
||
noteEl.style.top = `${startTop + dy}px`;
|
||
ensureNoteInViewport(noteEl);
|
||
};
|
||
|
||
const onPointerUp = () => {
|
||
if (!isDragging) return;
|
||
isDragging = false;
|
||
if (pointerId !== null && head.releasePointerCapture) {
|
||
try { head.releasePointerCapture(pointerId); } catch (e) { /* ignore */ }
|
||
}
|
||
head.removeEventListener('pointermove', onPointerMove);
|
||
head.removeEventListener('pointerup', onPointerUp);
|
||
head.removeEventListener('pointercancel', onPointerUp);
|
||
scheduleNoteSave(noteEl, 30);
|
||
};
|
||
|
||
head.addEventListener('pointerdown', (e) => {
|
||
if (e.target === pinBtn || e.target === addBtn) return;
|
||
if (noteEl.classList.contains('is-pinned')) return;
|
||
isDragging = true;
|
||
pointerId = e.pointerId;
|
||
dragStartX = e.clientX;
|
||
dragStartY = e.clientY;
|
||
startLeft = parseFloat(noteEl.style.left) || 0;
|
||
startTop = parseFloat(noteEl.style.top) || 0;
|
||
bringNoteToFront(noteEl);
|
||
if (head.setPointerCapture) {
|
||
try { head.setPointerCapture(pointerId); } catch (err) { /* ignore */ }
|
||
}
|
||
head.addEventListener('pointermove', onPointerMove);
|
||
head.addEventListener('pointerup', onPointerUp);
|
||
head.addEventListener('pointercancel', onPointerUp);
|
||
});
|
||
|
||
return noteEl;
|
||
}
|
||
|
||
if (homeNotesLayer) {
|
||
initialHomeNotes.forEach((note) => mountNote(note));
|
||
refreshDeleteButtonsState();
|
||
window.addEventListener('resize', () => {
|
||
homeNotesLayer.querySelectorAll('.home-user-note').forEach((noteEl) => ensureNoteInViewport(noteEl));
|
||
});
|
||
}
|
||
|
||
function attachDrag(modal, titlebar, closeBtn) {
|
||
if (!modal || !titlebar) return;
|
||
let isDragging = false;
|
||
let startX = 0;
|
||
let startY = 0;
|
||
let startLeft = 0;
|
||
let startTop = 0;
|
||
|
||
const onMove = (e) => {
|
||
if (!isDragging) return;
|
||
const nextLeft = startLeft + (e.clientX - startX);
|
||
const nextTop = startTop + (e.clientY - startY);
|
||
modal.style.left = `${nextLeft}px`;
|
||
modal.style.top = `${nextTop}px`;
|
||
};
|
||
|
||
const onUp = () => {
|
||
isDragging = false;
|
||
document.removeEventListener('mousemove', onMove);
|
||
document.removeEventListener('mouseup', onUp);
|
||
};
|
||
|
||
titlebar.addEventListener('mousedown', (e) => {
|
||
if (closeBtn && e.target === closeBtn) return;
|
||
isDragging = true;
|
||
const rect = modal.getBoundingClientRect();
|
||
startX = e.clientX;
|
||
startY = e.clientY;
|
||
startLeft = rect.left;
|
||
startTop = rect.top;
|
||
document.addEventListener('mousemove', onMove);
|
||
document.addEventListener('mouseup', onUp);
|
||
});
|
||
}
|
||
|
||
attachDrag(desktopModal, desktopTitlebar, desktopClose);
|
||
attachDrag(photoModal, photoTitlebar, photoClose);
|
||
attachDrag(searchInvModal, searchInvTitlebar, searchInvClose);
|
||
attachDrag(printersModal, printersTitlebar, printersClose);
|
||
attachDrag(computersModal, computersTitlebar, computersClose);
|
||
attachDrag(projectorsModal, projectorsTitlebar, projectorsClose);
|
||
attachDrag(docCameraModal, docCameraTitlebar, docCameraClose);
|
||
attachDrag(addDeviceModal, addDeviceTitlebar, addDeviceClose);
|
||
attachDrag(pcComponentsModal, pcComponentsTitlebar, pcComponentsClose);
|
||
attachDrag(usersModal, usersTitlebar, usersClose);
|
||
})();
|
||
</script>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|