diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc index d8e472b..419d40a 100644 Binary files a/api/routers/__pycache__/toolkit.cpython-313.pyc and b/api/routers/__pycache__/toolkit.cpython-313.pyc differ diff --git a/api/routers/toolkit.py b/api/routers/toolkit.py index 88bfff4..b53d1ae 100644 --- a/api/routers/toolkit.py +++ b/api/routers/toolkit.py @@ -14,9 +14,9 @@ async def toolkit_request( request_data: dict = Depends(requestDict), ): response = {"status": "error", "data": {}} - logger.info(f"Получение запроса остатка инструмента") - # logger.info(request_data) toolkitId = request_data.get("body").get("toolkitId") + logger.info(f"Получение запроса остатка инструмента #{toolkitId}") + # logger.info(request_data) stocks = await StockHandler.getByToolkitId(toolkitId) if not stocks: return response @@ -42,7 +42,6 @@ async def toolkit_request( "count": stock["quantity"], "placement": stock["placement"], } - logger.info(stock.keys()) if not toolboxesOwners.get(stock["toolbox_id"]): stocksData["toolboxes"][toolboxTitle]["id"] = stock["toolbox_id"] stocksData["toolboxes"][toolboxTitle]["totalCost"] = ( diff --git a/api/static/js/index.js b/api/static/js/index.js index 7eeae57..f8ed674 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -1047,6 +1047,7 @@ async function loadToolboxContent(toolboxId) { } } + async function fillToolbox(toolboxInfo) { const allToolkitsData = await apiRequest('/toolkit/fill_prepare'); @@ -1055,14 +1056,187 @@ async function fillToolbox(toolboxInfo) { return; } - // Проверяем, существует ли уже модальное окно + // Удаляем старое модальное окно let modal = document.getElementById('fillToolboxModal'); + if (modal) modal.remove(); - if (modal) { - modal.remove(); + // ============================== + // Подготовка данных + // ============================== + const { toolkits, categories, placements } = allToolkitsData.data; + + const placementMap = {}; + placements + .filter(p => p.toolbox_id === toolboxInfo.id) + .forEach(p => placementMap[p.toolkit_id] = p.placement); + + // lower -> category + const categoriesByLower = Object.fromEntries(categories.map(c => [c.title.toLowerCase().trim(), c])); + + const toolkitsMap = Object.fromEntries(toolkits.map(t => [t.id, t])); + + const toolkitsByCategory = {}; + toolkits.forEach(t => { + if (!toolkitsByCategory[t.category_id]) toolkitsByCategory[t.category_id] = []; + toolkitsByCategory[t.category_id].push(t); + }); + + // lower title -> toolkit within category + const toolkitsLowerByCategory = {}; + Object.entries(toolkitsByCategory).forEach(([catId, list]) => { + const map = {}; + list.forEach(t => map[t.title.toLowerCase().trim()] = t); + toolkitsLowerByCategory[catId] = map; + }); + + // ============================== + // Вспомогательные функции + // ============================== + const normalize = s => (s || '').toLowerCase().trim(); + + // exact match by lower title + const findCategoryByExact = title => { + if (!title) return null; + return categoriesByLower[title.toLowerCase().trim()] || null; + }; + + const findToolkitByExact = (categoryId, title) => { + if (!categoryId || !title) return null; + const map = toolkitsLowerByCategory[categoryId] || {}; + return map[title.toLowerCase().trim()] || null; + }; + + // format cost + const formatCost = value => { + if (typeof value !== 'number') value = parseFloat(value) || 0; + return formatPrice(value); + }; + + // debounce + function debounce(fn, delay = 200) { + let t; + return function (...args) { + clearTimeout(t); + t = setTimeout(() => fn.apply(this, args), delay); + }; } - // Создаем модальное окно + // Inject minimal styles for autocomplete dropdown (only once) + (function injectStyles() { + if (document.getElementById('fillToolboxAutocompleteStyles')) return; + const style = document.createElement('style'); + style.id = 'fillToolboxAutocompleteStyles'; + style.textContent = ` + .ft-autocomplete { position: relative; } + .ft-autocomplete-list { + position: absolute; + z-index: 2000; + left: 0; + right: 0; + max-height: 220px; + overflow: auto; + background: white; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + box-shadow: 0 .25rem .5rem rgba(0,0,0,.08); + } + .ft-autocomplete-item { padding: .375rem .5rem; cursor: pointer; } + .ft-autocomplete-item:hover, .ft-autocomplete-item.active { background: #7abb92ff; } + .ft-autocomplete-empty { padding: .375rem .5rem; color: #6c757d; } + `; + document.head.appendChild(style); + })(); + + // create suggestions list element for an input (returns container and helper functions) + function createAutocomplete(forInput) { + // wrapper ft-autocomplete should exist around input + let wrapper = forInput.closest('.ft-autocomplete'); + if (!wrapper) { + wrapper = document.createElement('div'); + wrapper.className = 'ft-autocomplete'; + forInput.parentNode.insertBefore(wrapper, forInput); + wrapper.appendChild(forInput); + } + + let list = wrapper.querySelector('.ft-autocomplete-list'); + if (!list) { + list = document.createElement('div'); + list.className = 'ft-autocomplete-list'; + list.style.display = 'none'; + wrapper.appendChild(list); + } + + function show(items) { + list.innerHTML = ''; + if (!items || items.length === 0) { + const empty = document.createElement('div'); + empty.className = 'ft-autocomplete-empty'; + empty.textContent = 'Ничего не найдено'; + list.appendChild(empty); + list.style.display = 'block'; + return; + } + items.forEach(it => { + const div = document.createElement('div'); + div.className = 'ft-autocomplete-item'; + div.textContent = it.title; + div.dataset.valueId = it.id; + div.addEventListener('mousedown', (e) => { + // mousedown чтобы сработало до blur + e.preventDefault(); + if (typeof wrapper._onSelect === 'function') wrapper._onSelect(it); + hide(); + }); + list.appendChild(div); + }); + list.style.display = 'block'; + } + + function hide() { + list.innerHTML = ''; + list.style.display = 'none'; + } + + function onSelect(fn) { wrapper._onSelect = fn; } + + return { wrapper, list, show, hide, onSelect }; + } + + // ============================== + // Создание строки таблицы + // ============================== + function createRow(rowIndex = 0) { + const rowId = `row-${Date.now()}-${rowIndex}`; + return ` + + +
+ +
+ + + +
+ +
+ + + + + + + + + + + `; + } + + // ============================== + // Создаём модалку + // ============================== modal = document.createElement('div'); modal.className = 'modal fade'; modal.id = 'fillToolboxModal'; @@ -1073,11 +1247,8 @@ async function fillToolbox(toolboxInfo) {