| | | 1 | | @using OzonOrdersWeb.ViewModels.OrderViewModels |
| | | 2 | | @model MultiplayEditOrderViewModel |
| | | 3 | | |
| | | 4 | | @{ |
| | | 5 | | // Дефолтная организация всегда Interparts (0000000000) |
| | | 6 | | const string DefaultInn = "0000000000"; |
| | | 7 | | const string DefaultName = "Interparts"; |
| | | 8 | | } |
| | | 9 | | |
| | | 10 | | <input type="hidden" id="oneCOrganizationInn" name="oneCOrganizationInn" value="@DefaultInn" /> |
| | | 11 | | |
| | | 12 | | <span class="text-muted small ms-1"> |
| | | 13 | | Организация: |
| | | 14 | | <a href="#" id="oneCOrgSelectorLink" class="text-decoration-none fw-semibold" |
| | | 15 | | data-bs-toggle="modal" data-bs-target="#oneCOrgModal"> |
| | 0 | 16 | | @DefaultName (ИНН: @DefaultInn) |
| | | 17 | | </a> |
| | | 18 | | </span> |
| | | 19 | | |
| | | 20 | | <!-- Модалка выбора организации 1С --> |
| | | 21 | | <div class="modal fade" id="oneCOrgModal" tabindex="-1" aria-labelledby="oneCOrgModalLabel" aria-hidden="true"> |
| | | 22 | | <div class="modal-dialog modal-dialog-centered"> |
| | | 23 | | <div class="modal-content"> |
| | | 24 | | <div class="modal-header"> |
| | | 25 | | <h5 class="modal-title" id="oneCOrgModalLabel"> |
| | | 26 | | <i class="fas fa-building me-2"></i>Организация для проведения в 1С |
| | | 27 | | </h5> |
| | | 28 | | <button type="button" class="btn-close" data-bs-dismiss="modal"></button> |
| | | 29 | | </div> |
| | | 30 | | <div class="modal-body"> |
| | | 31 | | <p class="text-muted small mb-3"> |
| | | 32 | | По умолчанию используется <strong>Interparts (0000000000)</strong>. Измените только если нужна друга |
| | | 33 | | </p> |
| | | 34 | | <div class="mb-1 position-relative"> |
| | | 35 | | <label class="form-label fw-semibold">Поиск организации</label> |
| | | 36 | | <input type="text" id="oneCOrgSearch" class="form-control" placeholder="Введите ИНН или название…" |
| | | 37 | | <input type="hidden" id="oneCOrgTempInn" value="" /> |
| | | 38 | | <input type="hidden" id="oneCOrgTempName" value="" /> |
| | | 39 | | <div id="oneCOrgResults" |
| | | 40 | | class="position-absolute w-100 border rounded shadow-sm bg-white" |
| | | 41 | | style="display:none; z-index:9999; max-height:220px; overflow-y:auto; top:100%;"> |
| | | 42 | | </div> |
| | | 43 | | </div> |
| | | 44 | | <div id="oneCOrgSelectedRow" class="mt-2 text-success small" style="display:none"> |
| | | 45 | | <i class="fas fa-check-circle me-1"></i><span id="oneCOrgSelectedText"></span> |
| | | 46 | | </div> |
| | | 47 | | </div> |
| | | 48 | | <div class="modal-footer d-flex justify-content-between align-items-center"> |
| | | 49 | | <button type="button" class="btn btn-outline-secondary btn-sm" id="oneCOrgResetBtn"> |
| | | 50 | | Сбросить (Interparts) |
| | | 51 | | </button> |
| | | 52 | | <div> |
| | | 53 | | <button type="button" class="btn btn-secondary me-1" data-bs-dismiss="modal">Отмена</button> |
| | | 54 | | <button type="button" class="btn btn-primary" id="oneCOrgConfirmBtn" data-bs-dismiss="modal" disable |
| | | 55 | | Применить |
| | | 56 | | </button> |
| | | 57 | | </div> |
| | | 58 | | </div> |
| | | 59 | | </div> |
| | | 60 | | </div> |
| | | 61 | | </div> |
| | | 62 | | |
| | | 63 | | <script> |
| | | 64 | | (function () { |
| | | 65 | | const ENDPOINT = '/Studio2/OzonClients/SearchOrganizations'; |
| | | 66 | | const realInn = document.getElementById('oneCOrganizationInn'); |
| | | 67 | | const linkEl = document.getElementById('oneCOrgSelectorLink'); |
| | | 68 | | const searchEl = document.getElementById('oneCOrgSearch'); |
| | | 69 | | const tempInn = document.getElementById('oneCOrgTempInn'); |
| | | 70 | | const tempName = document.getElementById('oneCOrgTempName'); |
| | | 71 | | const resultsEl = document.getElementById('oneCOrgResults'); |
| | | 72 | | const selectedRow = document.getElementById('oneCOrgSelectedRow'); |
| | | 73 | | const selectedText = document.getElementById('oneCOrgSelectedText'); |
| | | 74 | | const confirmBtn = document.getElementById('oneCOrgConfirmBtn'); |
| | | 75 | | const resetBtn = document.getElementById('oneCOrgResetBtn'); |
| | | 76 | | |
| | | 77 | | let timer = null; |
| | | 78 | | |
| | | 79 | | // ── Утилиты ────────────────────────────────────────────────────────────── |
| | | 80 | | const esc = s => (s || '').replace(/[&<>"']/g, c => |
| | | 81 | | ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); |
| | | 82 | | |
| | | 83 | | function hideResults() { resultsEl.style.display = 'none'; } |
| | | 84 | | |
| | | 85 | | function markPending(inn, name) { |
| | | 86 | | tempInn.value = inn; |
| | | 87 | | tempName.value = name; |
| | | 88 | | selectedText.textContent = name + (inn ? ' (ИНН: ' + inn + ')' : ''); |
| | | 89 | | selectedRow.style.display = ''; |
| | | 90 | | confirmBtn.disabled = false; |
| | | 91 | | } |
| | | 92 | | |
| | | 93 | | // ── Рендер выпадающего списка ───────────────────────────────────────────── |
| | | 94 | | function renderItems(items, q) { |
| | | 95 | | if (!items.length) { |
| | | 96 | | resultsEl.innerHTML = '<div class="px-3 py-2 text-muted small fst-italic">Не найдено</div>'; |
| | | 97 | | resultsEl.style.display = 'block'; |
| | | 98 | | return; |
| | | 99 | | } |
| | | 100 | | const hi = t => !q ? esc(t) : esc(t).replace( |
| | | 101 | | new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&') + ')', 'gi'), |
| | | 102 | | '<mark class="bg-warning-subtle rounded px-0">$1</mark>'); |
| | | 103 | | resultsEl.innerHTML = items.map((it, i) => ` |
| | | 104 | | <div class="one-c-org-item d-flex flex-column px-3 py-2 border-bottom" tabindex="0" data-i="${i}" |
| | | 105 | | data-inn="${esc(it.inn)}" data-name="${esc(it.name)}"> |
| | | 106 | | <span class="fw-semibold small font-monospace">${hi(it.inn || '')}</span> |
| | | 107 | | <span class="text-muted small">${hi(it.name || '')}</span> |
| | | 108 | | </div>`).join(''); |
| | | 109 | | resultsEl.querySelectorAll('.one-c-org-item').forEach(el => { |
| | | 110 | | el.addEventListener('mousedown', e => { |
| | | 111 | | e.preventDefault(); |
| | | 112 | | markPending(el.dataset.inn, el.dataset.name); |
| | | 113 | | searchEl.value = el.dataset.inn ? `${el.dataset.inn} — ${el.dataset.name}` : el.dataset.name; |
| | | 114 | | hideResults(); |
| | | 115 | | }); |
| | | 116 | | el.addEventListener('keydown', e => { |
| | | 117 | | if (e.key === 'Enter') { e.preventDefault(); el.dispatchEvent(new MouseEvent('mousedown')); } |
| | | 118 | | if (e.key === 'ArrowDown') (el.nextElementSibling || el).focus(); |
| | | 119 | | if (e.key === 'ArrowUp') (el.previousElementSibling || el).focus(); |
| | | 120 | | if (e.key === 'Escape') { hideResults(); searchEl.focus(); } |
| | | 121 | | }); |
| | | 122 | | }); |
| | | 123 | | resultsEl.style.display = 'block'; |
| | | 124 | | } |
| | | 125 | | |
| | | 126 | | // ── Поиск ───────────────────────────────────────────────────────────────── |
| | | 127 | | async function doSearch(q) { |
| | | 128 | | resultsEl.innerHTML = '<div class="px-3 py-2 text-muted small"><i class="fas fa-circle-notch fa-spin me-1"></i>П |
| | | 129 | | resultsEl.style.display = 'block'; |
| | | 130 | | try { |
| | | 131 | | const r = await fetch(`${ENDPOINT}?q=${encodeURIComponent(q)}&limit=15`); |
| | | 132 | | if (!r.ok) throw new Error(r.statusText); |
| | | 133 | | renderItems(await r.json(), q); |
| | | 134 | | } catch (err) { |
| | | 135 | | resultsEl.innerHTML = `<div class="px-3 py-2 text-danger small">Ошибка: ${esc(err.message)}</div>`; |
| | | 136 | | } |
| | | 137 | | } |
| | | 138 | | |
| | | 139 | | searchEl.addEventListener('input', function () { |
| | | 140 | | const q = this.value.trim(); |
| | | 141 | | clearTimeout(timer); |
| | | 142 | | if (q.length < 2) { hideResults(); return; } |
| | | 143 | | timer = setTimeout(() => doSearch(q), 280); |
| | | 144 | | }); |
| | | 145 | | searchEl.addEventListener('keydown', e => { |
| | | 146 | | if (e.key === 'ArrowDown') resultsEl.querySelector('.one-c-org-item')?.focus(); |
| | | 147 | | if (e.key === 'Escape') hideResults(); |
| | | 148 | | }); |
| | | 149 | | document.addEventListener('click', e => { |
| | | 150 | | if (!resultsEl.contains(e.target) && e.target !== searchEl) hideResults(); |
| | | 151 | | }); |
| | | 152 | | |
| | | 153 | | // ── Применить ───────────────────────────────────────────────────────────── |
| | | 154 | | confirmBtn.addEventListener('click', function () { |
| | | 155 | | if (!tempInn.value) return; |
| | | 156 | | realInn.value = tempInn.value; |
| | | 157 | | linkEl.textContent = tempName.value + ' (ИНН: ' + tempInn.value + ')'; |
| | | 158 | | }); |
| | | 159 | | |
| | | 160 | | // ── Сброс ───────────────────────────────────────────────────────────────── |
| | | 161 | | resetBtn.addEventListener('click', function () { |
| | | 162 | | realInn.value = '0000000000'; |
| | | 163 | | linkEl.textContent = 'Interparts (ИНН: 0000000000)'; |
| | | 164 | | searchEl.value = ''; |
| | | 165 | | tempInn.value = ''; |
| | | 166 | | tempName.value = ''; |
| | | 167 | | selectedRow.style.display = 'none'; |
| | | 168 | | confirmBtn.disabled = true; |
| | | 169 | | hideResults(); |
| | | 170 | | }); |
| | | 171 | | |
| | | 172 | | // ── Открытие модалки — сбрасываем «pending» ─────────────────────────────── |
| | | 173 | | document.getElementById('oneCOrgModal').addEventListener('show.bs.modal', function () { |
| | | 174 | | searchEl.value = ''; |
| | | 175 | | tempInn.value = ''; |
| | | 176 | | tempName.value = ''; |
| | | 177 | | selectedRow.style.display = 'none'; |
| | | 178 | | confirmBtn.disabled = true; |
| | | 179 | | hideResults(); |
| | | 180 | | }); |
| | | 181 | | })(); |
| | | 182 | | </script> |