import { getCookie } from '/static/js/cookies.js'; import { apiRequest } from '/static/js/api.js'; import { showInfo } from '/static/js//toast.js'; let accessData; let userData; let currentToolboxData = null; async function getCookieData() { accessData = await getCookie('toolbox_access'); userData = await getCookie('toolbox_user'); } async function openTab(event, tabId) { // Убираем активный класс со всех вкладок и кнопок document.querySelectorAll('.tab-nav-btn').forEach(btn => { btn.classList.remove('active'); btn.querySelector('.nav-icon').classList.remove('text-primary'); btn.querySelector('.nav-icon').classList.add('text-muted'); }); document.querySelectorAll('.tab-pane').forEach(pane => { pane.classList.remove('show', 'active'); }); // Добавляем активный класс выбранной вкладке и кнопке event.currentTarget.classList.add('active'); event.currentTarget.querySelector('.nav-icon').classList.remove('text-muted'); event.currentTarget.querySelector('.nav-icon').classList.add('text-primary'); document.getElementById(tabId).classList.add('show', 'active'); await uploadTab(tabId); } function prepareTabs() { let tabsData = { 'toolbox': { title: 'Склад', icon: 'bi-box-seam', description: 'Управление остатками инструмента на складе' }, 'toolkits': { title: 'Инструменты', icon: 'bi-tools', description: 'Каталог инструментов' }, }; if (accessData.available_own_toolbox) { tabsData['requests'] = { title: 'Запросы', icon: 'bi-chat-left-text', description: 'Управление запросами на инструменты' }; } if (accessData.view_services) { tabsData['jurnal_toolkits'] = { title: 'Журнал перемещений', icon: 'bi-journal-text', description: 'Журнал перемещений инструментов' }; } if (accessData.view_requests) { tabsData['jurnal_service'] = { title: 'Сервисный журнал', icon: 'bi-journal-richtext', description: 'Журнал сервисных запросов' }; } if (accessData.users_view) { tabsData['users'] = { title: 'Пользователи', icon: 'bi-people', description: 'Управление пользователями' }; } const tabs = `
${Object.entries(tabsData).map(([tabId, tabData]) => `

${tabData.title}

${tabData.description}

Загрузка...
`).join('')}
`; const mainContainer = document.getElementById('mainContent'); mainContainer.insertAdjacentHTML('afterbegin', tabs); } async function uploadTab(tabId) { const cookiesData = { userData, accessData }; try { const resp = await apiRequest('/', { tabId, cookiesData }); if (resp.status == 'ok') { fillTab(tabId, resp.data); } else { throw new Error(resp.message || 'Ошибка загрузки данных'); } } catch (error) { console.error('Error loading tab:', error); const tabContent = document.getElementById(`${tabId}-tab-content`); tabContent.innerHTML = ` `; } } function fillTab(tabId, tabData) { try { switch (tabId) { case 'toolbox': renderToolboxTab(tabData); break; case 'requests': renderSimpleTab(tabId, tabData, 'Запросы на инструменты'); break; case 'toolkits': renderToolkitsTab(tabId, tabData.toolkits, tabData.categories); break; case 'jurnal_toolkits': renderSimpleTab(tabId, tabData, 'Журнал перемещений'); break; case 'jurnal_service': renderSimpleTab(tabId, tabData, 'Сервисный журнал'); break; case 'users': renderSimpleTab(tabId, tabData, 'Пользователи системы'); break; } } catch (error) { console.error('Error filling tab:', error); const tabContent = document.getElementById(`${tabId}-tab-content`); tabContent.innerHTML = ` `; } } function renderSimpleTab(tabId, tabData, title) { const tabContent = document.getElementById(`${tabId}-tab-content`); tabContent.innerHTML = `
${title}
${Object.entries(tabData).map(([key, value]) => `
${key}

${typeof value === 'object' ? JSON.stringify(value, null, 4) : value}

`).join('')}
`; } function renderToolkitsTab(tabId, toolsArray, categoriesList) { const tabContent = document.getElementById(`${tabId}-tab-content`); // Преобразуем объект в массив, если передан объект const tools = Array.isArray(toolsArray) ? toolsArray : Object.values(toolsArray); let uniqueCategories = {}; categoriesList.forEach(cat => { uniqueCategories[cat.id] = { id: cat.id, title: cat.title, description: cat.description }; }); tools.forEach(tool => { tool['category'] = uniqueCategories[tool.category_id]?.title || ''; tool['category_desc'] = uniqueCategories[tool.category_id]?.description || ''; }); // Сортируем инструменты: сначала по названию категории, затем по названию const sortedTools = [...tools].sort((a, b) => { // Получаем названия категорий const catA = uniqueCategories[a.category_id]?.title || ''; const catB = uniqueCategories[b.category_id]?.title || ''; // Сначала сравниваем категории if (catA < catB) return -1; if (catA > catB) return 1; // Если категории одинаковые, сравниваем названия инструментов const titleA = a.title || ''; const titleB = b.title || ''; return titleA.localeCompare(titleB, 'ru'); }); // Создаем HTML структуру tabContent.innerHTML = `
${Object.values(uniqueCategories).map(category => ` `).join('')}
`; // Рендерим карточки renderToolkitCards(tabId, sortedTools, uniqueCategories); // Добавляем обработчики событий для фильтров setupFilters(tabId, tools, uniqueCategories); } // Функция для рендеринга карточек function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', categoryFilter = 'all') { const container = document.getElementById(`${tabId}-cards-container`); // Фильтруем инструменты const filteredTools = tools.filter(tool => { // Фильтр по категории const categoryMatch = categoryFilter === 'all' || tool.category_id == categoryFilter; // Фильтр по тексту const searchMatch = !filterText || (tool.title && tool.title.toLowerCase().includes(filterText.toLowerCase())) || (tool.description && tool.description.toLowerCase().includes(filterText.toLowerCase())); return categoryMatch && searchMatch; }); // Рендерим карточки if (filteredTools.length === 0) { container.innerHTML = `
Ничего не найдено. Попробуйте изменить параметры фильтрации.
`; return; } container.innerHTML = filteredTools.map(tool => { const categoryName = categoriesMap[tool.category_id]?.title || `Категория ${tool.category_id}`; const description = tool.description || 'Описание отсутствует'; const imageUrl = tool.image?.main || 'static/images/tools/default.png'; return `
${tool.title || 'Инструмент'} ${categoryName}
${tool.title || 'Без названия'}

${description}

${tool.quantity_min ? ` Мин: ${tool.quantity_min} ${tool.quantity_min_extra ? `( ${tool.quantity_min_extra})` : ''} ` : ''}
`; }).join(''); const cards = container.querySelectorAll('.toolkit-card'); cards.forEach(card => { card.addEventListener('click', async event => { const toolId = event.currentTarget.dataset.toolid; const tool = tools.find(t => t.id == toolId); await showToolkitDetailsModal(tool); }); }); } // Функция для настройки фильтров function setupFilters(tabId, tools, categoriesMap) { const container = document.getElementById(`${tabId}-cards-container`); const searchInput = document.getElementById(`${tabId}-search-input`); const filterButtons = document.querySelectorAll(`#${tabId}-tab-content .filter-btn`); // Текущие значения фильтров let currentFilter = { category: 'all', search: '' }; // Обработчик для кнопок категорий filterButtons.forEach(button => { button.addEventListener('click', function () { // Убираем активный класс у всех кнопок filterButtons.forEach(btn => btn.classList.remove('active')); // Добавляем активный класс текущей кнопке this.classList.add('active'); currentFilter.category = this.dataset.category; renderToolkitCards(tabId, tools, categoriesMap, currentFilter.search, currentFilter.category); }); }); // Обработчик для поля поиска if (searchInput) { let searchTimeout; searchInput.addEventListener('input', function () { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { currentFilter.search = this.value.trim(); renderToolkitCards(tabId, tools, categoriesMap, currentFilter.search, currentFilter.category); }, 300); }); // Очистка поиска searchInput.insertAdjacentHTML('afterend', ` `); const clearBtn = document.getElementById(`${tabId}-clear-search`); if (clearBtn) { clearBtn.addEventListener('click', function () { searchInput.value = ''; currentFilter.search = ''; renderToolkitCards(tabId, tools, categoriesMap, '', currentFilter.category); this.classList.add('d-none'); }); searchInput.addEventListener('input', function () { clearBtn.classList.toggle('d-none', !this.value); }); } } } function addToolbox() { // Проверяем, существует ли уже модальное окно let modal = document.getElementById('addToolboxModal'); if (modal) { modal.remove(); } // Создаем модальное окно modal = document.createElement('div'); modal.className = 'modal fade'; modal.id = 'addToolboxModal'; modal.tabIndex = -1; modal.setAttribute('aria-hidden', 'true'); modal.innerHTML = ` `; document.body.appendChild(modal); // Инициализация модального окна const bsModal = new bootstrap.Modal(modal); // Получаем элементы формы const form = modal.querySelector('#addToolboxForm'); const titleInput = modal.querySelector('#toolboxTitle'); const descriptionInput = modal.querySelector('#toolboxDescription'); const submitBtn = modal.querySelector('#submitToolboxBtn'); const spinner = modal.querySelector('#submitToolboxSpinner'); // Функция валидации формы function validateForm() { let isValid = true; // Валидация названия if (titleInput.value.length < 3) { titleInput.classList.add('is-invalid'); isValid = false; } else { titleInput.classList.remove('is-invalid'); titleInput.classList.add('is-valid'); } // Валидация описания (необязательно, но если заполнено - проверяем) if (descriptionInput.value.length > 0 && descriptionInput.value.length < 3) { descriptionInput.classList.add('is-invalid'); isValid = false; } else if (descriptionInput.value.length >= 3) { descriptionInput.classList.remove('is-invalid'); descriptionInput.classList.add('is-valid'); } else { descriptionInput.classList.remove('is-invalid', 'is-valid'); } submitBtn.disabled = !isValid; return isValid; } // Слушатели событий для валидации в реальном времени titleInput.addEventListener('input', function () { if (this.value.length >= 3) { this.classList.remove('is-invalid'); this.classList.add('is-valid'); } else { this.classList.remove('is-valid'); if (this.value.length > 0) { this.classList.add('is-invalid'); } else { this.classList.remove('is-invalid'); } } validateForm(); }); descriptionInput.addEventListener('input', function () { if (this.value.length === 0) { this.classList.remove('is-invalid', 'is-valid'); } else if (this.value.length >= 3) { this.classList.remove('is-invalid'); this.classList.add('is-valid'); } else { this.classList.add('is-invalid'); this.classList.remove('is-valid'); } validateForm(); }); // Обработчик отправки формы form.addEventListener('submit', async function (e) { e.preventDefault(); if (!validateForm()) { return; } // Показываем индикатор загрузки и отключаем кнопку submitBtn.disabled = true; spinner.style.display = 'inline-block'; // Собираем данные const toolboxData = { title: titleInput.value.trim(), description: descriptionInput.value.trim(), monitoring: modal.querySelector('#toolboxMonitoring').checked }; const userId = userData.id; try { // Отправка данных (замените на ваш реальный endpoint) const response = await apiRequest("/toolbox/", { toolboxData, userId }); if (response.status !== 'ok') { throw new Error('Ошибка при добавлении склада'); } // Успешная отправка bsModal.hide(); // Показываем уведомление об успехе showInfo('Склад успешно добавлен', 'success'); // Здесь можно добавить обновление списка складов await uploadTab('toolbox'); } catch (error) { console.error('Ошибка при добавлении склада:', error); // Возвращаем кнопку в исходное состояние submitBtn.disabled = false; spinner.style.display = 'none'; // Показываем сообщение об ошибке showInfo('Ошибка при добавлении склада. Попробуйте еще раз.', 'error'); // Можно добавить более детальное сообщение об ошибке const errorDiv = document.createElement('div'); errorDiv.className = 'alert alert-danger mt-3'; errorDiv.innerHTML = ` Ошибка! Не удалось добавить склад. Проверьте соединение и попробуйте еще раз. `; const modalBody = modal.querySelector('.modal-body'); if (!modalBody.querySelector('.alert')) { modalBody.appendChild(errorDiv); // Убираем сообщение через 5 секунд setTimeout(() => { if (errorDiv.parentNode) { errorDiv.remove(); } }, 5000); } } }); // Очистка при закрытии модального окна modal.addEventListener('hidden.bs.modal', () => { // Сбрасываем форму form.reset(); // Убираем стили валидации titleInput.classList.remove('is-valid', 'is-invalid'); descriptionInput.classList.remove('is-valid', 'is-invalid'); // Включаем кнопку submitBtn.disabled = false; spinner.style.display = 'none'; // Убираем сообщения об ошибках const alerts = modal.querySelectorAll('.alert'); alerts.forEach(alert => alert.remove()); // Удаляем модальное окно из DOM setTimeout(() => { if (modal.parentNode) { modal.remove(); } }, 300); }); // Показываем модальное окно bsModal.show(); // Фокусируемся на первом поле setTimeout(() => { titleInput.focus(); }, 500); } function renderToolboxTab(tabData) { currentToolboxData = tabData; const tabContent = document.getElementById(`toolbox-tab-content`); const tabOptionalContent = document.getElementById(`toolbox-tab-optional-content`); if (!tabData || tabData.length === 0) { tabContent.innerHTML = `

Нет доступных складов

У вас нет доступа ни к одному складу

`; return; } // Создаем навигацию по складам const toolboxNav = `
${tabData.map((toolbox, index) => ` `).join('')}
`; // Создаем контейнер для содержимого склада const toolboxContent = `

Выберите склад для просмотра

Для отображения содержимого склада нажмите на одну из кнопок выше

`; tabOptionalContent.innerHTML = toolboxNav; if (accessData.manage_toolboxes) { const addToolboxBtn = document.createElement('button'); addToolboxBtn.className = 'btn btn-outline-success toolbox-nav-btn d-flex align-items-center mb-2'; addToolboxBtn.innerHTML = ` Добавить `; addToolboxBtn.addEventListener('click', function (e) { e.preventDefault(); addToolbox(); }); document.getElementById('toolboxNav').appendChild(addToolboxBtn); } tabContent.innerHTML = toolboxContent; } // Функция для выбора склада window.selectToolbox = async function (toolboxId, index) { // Убираем активный класс со всех кнопок складов document.querySelectorAll('.toolbox-nav-btn').forEach(btn => { btn.classList.remove('active'); }); // Добавляем активный класс выбранной кнопке const selectedBtn = document.querySelector(`.toolbox-nav-btn[data-toolbox-id="${toolboxId}"]`); if (selectedBtn) { selectedBtn.classList.add('active'); } // Загружаем содержимое склада await loadToolboxContent(toolboxId); } async function loadToolboxContent(toolboxId) { const contentContainer = document.querySelector('.toolbox-content-container'); // Показываем индикатор загрузки contentContainer.innerHTML = `
Загрузка...

Загрузка содержимого склада

Пожалуйста, подождите...

`; try { const resp = await apiRequest(`/stocks/`, { toolboxId }); if (resp.status === 'ok') { const toolboxData = resp.data; const toolboxInfo = currentToolboxData.find(t => t.id === toolboxId); if (toolboxData.length === 0) { contentContainer.innerHTML = `

Склад пуст

В этом складе нет инструментов

`; if (!toolboxInfo.owner_id && accessData.manage_toolboxes) { document.getElementById('deleteToolbox').addEventListener('click', function (e) { e.preventDefault(); deleteToolbox(toolboxId); }); } else { document.getElementById('deleteToolbox').remove(); } return; } // Находим информацию о выбранном складе const toolboxOwn = toolboxInfo.owner_id === userData.id ? 'Мой склад' : toolboxInfo.owner_id ? 'Склад сотрудника' : 'Общий склад'; const quantityMonitoring = toolboxInfo.monitoring && accessData.view_all_toolboxes; // Обрабатываем данные в единый список const processedData = processToolboxData(toolboxData, toolboxId, quantityMonitoring); const totalQuantity = processedData.reduce((sum, item) => sum + item.totalQuantity, 0); const totalCost = formatPrice(processedData.reduce((sum, item) => sum + item.totalCost, 0)); // Отображаем содержимое склада contentContainer.innerHTML = `
${toolboxInfo?.title || 'Склад'}

${toolboxInfo?.description || 'Описание отсутствует'}

${toolboxOwn}
Количество позиций: ${processedData.length} Количество инструментов: ${totalQuantity} Общая стоимость: ${totalCost}
${quantityMonitoring ? '' : ''} ${toolboxOwn === 'Общий склад' ? `` : ''}
Название Категория Количество Статус Стоимость Расположение Последнее изменение
`; // Инициализация таблицы с данными await initializeToolboxTable(processedData, toolboxOwn, quantityMonitoring); } else { throw new Error(resp.message || 'Ошибка загрузки данных склада'); } } catch (error) { console.error('Error loading toolbox content:', error); contentContainer.innerHTML = `

Ошибка загрузки

Не удалось загрузить содержимое склада
${error.message}

`; } } async function deleteToolbox(toolboxId) { // Находим информацию о складе const toolboxInfo = currentToolboxData.find(t => t.id === toolboxId); if (!toolboxInfo) { showInfo('Склад не найден', 'error'); return; } // Проверяем, существует ли уже модальное окно let modal = document.getElementById('deleteToolboxModal'); if (modal) { modal.remove(); } // Создаем модальное окно подтверждения modal = document.createElement('div'); modal.className = 'modal fade'; modal.id = 'deleteToolboxModal'; modal.tabIndex = -1; modal.setAttribute('aria-hidden', 'true'); modal.innerHTML = ` `; document.body.appendChild(modal); // Инициализация модального окна const bsModal = new bootstrap.Modal(modal); // Получаем элементы const confirmCheckbox = modal.querySelector('#confirmDeleteCheckbox'); const confirmBtn = modal.querySelector('#confirmDeleteBtn'); const deleteSpinner = modal.querySelector('#deleteSpinner'); // Активация кнопки при подтверждении confirmCheckbox.addEventListener('change', function () { confirmBtn.disabled = !this.checked; }); // Обработчик кнопки удаления confirmBtn.addEventListener('click', async function () { if (!confirmCheckbox.checked) return; // Показываем индикатор загрузки и отключаем кнопку confirmBtn.disabled = true; deleteSpinner.style.display = 'inline-block'; try { // Отправляем запрос на удаление const userId = userData.id; const resp = await apiRequest('/toolbox/', { toolboxId, userId }, 'DELETE'); // Проверяем успешность запроса if (resp.status == 'ok') { // Успешное удаление bsModal.hide(); showInfo('Склад успешно удален', 'success'); await uploadTab('toolbox'); } else { // Обработка ошибок от сервера let errorMessage = 'Не удалось удалить склад'; if (resp.message) { errorMessage += ': ' + resp.message; } // Показываем конкретное сообщение об ошибке showInfo(errorMessage, 'error'); // Возвращаем кнопку в исходное состояние confirmBtn.disabled = false; deleteSpinner.style.display = 'none'; } } catch (error) { console.error('Ошибка при удалении склада:', error); // Возвращаем кнопку в исходное состояние confirmBtn.disabled = false; deleteSpinner.style.display = 'none'; // Показываем общее сообщение об ошибке showInfo('Произошла ошибка при удалении склада. Попробуйте еще раз.', 'error'); } }); // Очистка при закрытии модального окна modal.addEventListener('hidden.bs.modal', () => { // Удаляем модальное окно из DOM setTimeout(() => { if (modal.parentNode) { modal.remove(); } }, 300); }); // Показываем модальное окно bsModal.show(); } // Функция обработки данных склада function processToolboxData(toolboxData, toolboxId, quantityMonitoring) { const { stocks, toolkits, categories } = toolboxData; // Создаем мапы для быстрого доступа const toolkitMap = {}; const categoryMap = {}; toolkits.forEach(toolkit => { toolkitMap[toolkit.id] = toolkit; }); categories.forEach(category => { categoryMap[category.id] = category; }); // Группируем стоки по инструментам const groupedStocks = {}; stocks.forEach(stock => { if (stock.toolbox_id !== toolboxId) return; const toolkitId = stock.toolkit_id; if (!groupedStocks[toolkitId]) { groupedStocks[toolkitId] = { stocks: [], placements: new Set() }; } groupedStocks[toolkitId].stocks.push(stock); if (stock.placement) { groupedStocks[toolkitId].placements.add(stock.placement); } }); // Формируем итоговый массив const result = []; Object.keys(groupedStocks).forEach(toolkitId => { const toolkit = toolkitMap[toolkitId]; if (!toolkit) return; const group = groupedStocks[toolkitId]; const category = categoryMap[toolkit.category_id]; // Рассчитываем общие показатели const totalQuantity = group.stocks.reduce((sum, stock) => sum + stock.quantity, 0); const totalCost = group.stocks.reduce((sum, stock) => sum + (stock.quantity * stock.price), 0); // Определяем статус достаточности let indicator = null; if (quantityMonitoring) { if (totalQuantity >= toolkit.quantity_min) { indicator = { text: 'Достаточно', class: 'success' }; } else if (totalQuantity >= toolkit.quantity_min_extra) { indicator = { text: 'Мало', class: 'warning' }; } else { indicator = { text: 'Критически мало', class: 'danger' }; } } // Формируем расположение let placement = group.placements.size > 0 ? Array.from(group.placements).join(', ') : 'Своб. расположение'; // Находим дату последнего изменения const lastUpdated = group.stocks.reduce((latest, stock) => { const stockDate = new Date(stock.updated_at); return stockDate > latest ? stockDate : latest; }, new Date(0)); result.push({ id: parseInt(toolkitId), toolboxId: toolboxId, image: toolkit.image?.main || 'static/images/tools/default.png', images: toolkit.image?.additional || [], title: toolkit.title, category: category?.title || 'Без категории', totalQuantity: totalQuantity, indicator: indicator, totalCost: totalCost, // Сохраняем число, форматируем при выводе placement: placement, lastUpdated: lastUpdated.toLocaleString('ru-RU'), available: totalQuantity, // для проверки при операциях toolkitData: toolkit, // для модального окна categoryData: category // для модального окна }); }); return result; } // Функция форматирования стоимости с разделителями тысяч function formatPrice(price) { return parseFloat(price).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); } // Функция инициализации таблицы async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) { let currentPage = 1; const itemsPerPage = 20; let currentSort = { field: 'title', direction: 'asc' }; let filteredData = [...data]; // Инициализация пагинации async function initializePagination() { const totalPages = Math.ceil(filteredData.length / itemsPerPage); const paginationContainer = document.getElementById('toolboxPagination'); const tbody = document.getElementById('toolboxItemsBody'); // Очищаем текущее содержимое paginationContainer.innerHTML = ''; // Добавляем кнопки пагинации const prevBtn = document.createElement('li'); prevBtn.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`; prevBtn.innerHTML = `Назад`; prevBtn.addEventListener('click', (e) => { e.preventDefault(); if (currentPage > 1) { currentPage--; renderTable(); } }); paginationContainer.appendChild(prevBtn); // Определяем диапазон отображаемых страниц let startPage = Math.max(1, currentPage - 2); let endPage = Math.min(totalPages, currentPage + 2); for (let i = startPage; i <= endPage; i++) { const pageItem = document.createElement('li'); pageItem.className = `page-item ${i === currentPage ? 'active' : ''}`; pageItem.innerHTML = `${i}`; pageItem.addEventListener('click', (e) => { e.preventDefault(); currentPage = i; renderTable(); }); paginationContainer.appendChild(pageItem); } const nextBtn = document.createElement('li'); nextBtn.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`; nextBtn.innerHTML = `Вперед`; nextBtn.addEventListener('click', (e) => { e.preventDefault(); if (currentPage < totalPages) { currentPage++; renderTable(); } }); paginationContainer.appendChild(nextBtn); // Рендерим данные текущей страницы await renderTable(); } // Функция рендеринга таблицы async function renderTable() { const tbody = document.getElementById('toolboxItemsBody'); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const pageData = filteredData.slice(startIndex, endIndex); tbody.innerHTML = ''; pageData.forEach(item => { const tr = document.createElement('tr'); tr.dataset.id = item.id; tr.dataset.quantity = item.totalQuantity; // Определяем, какие кнопки показывать let actionButtons = ''; if (toolboxOwn === 'Мой склад' || toolboxOwn === 'Склад сотрудника') { actionButtons = `
`; } else if (toolboxOwn === 'Общий склад' && accessData.available_own_toolbox) { actionButtons = `
`; } tr.innerHTML = ` ${item.title} ${item.title}
${actionButtons} ${item.category} ${item.totalQuantity} ${quantityMonitoring ? `${item.indicator?.text || '-'}` : ''} ${formatPrice(item.totalCost)} ₽ ${toolboxOwn === 'Общий склад' ? `${item.placement}` : ''} ${item.lastUpdated} `; tbody.appendChild(tr); // Добавляем обработчики для кнопок в строке const actionBtn = tr.querySelector('.action-buttons'); if (actionBtn) { actionBtn.querySelectorAll('button[data-action]').forEach(button => { button.addEventListener('click', async (e) => { e.stopPropagation(); const action = e.currentTarget.dataset.action; const itemId = e.currentTarget.dataset.id; const selectedItem = data.find(d => d.id == itemId); if (selectedItem) { await showOperationModal(action, selectedItem); } }); }); } }); // Добавляем обработчики для изображений document.querySelectorAll('.toolkit-image-link').forEach(link => { link.addEventListener('click', async (e) => { e.preventDefault(); const itemId = e.currentTarget.dataset.id; const item = data.find(d => d.id == itemId); if (item) { await showToolkitDetailsModal(item); } }); }); } function parseDate(d) { // d = "07.12.2025, 13:19:20" const [datePart, timePart] = d.split(', '); const [day, month, year] = datePart.split('.').map(Number); const [hour, minute, second] = timePart.split(':').map(Number); return new Date(year, month - 1, day, hour, minute, second); } // Функция сортировки function sortData(field, direction) { filteredData.sort((a, b) => { let aValue = a[field]; let bValue = b[field]; // Для числовых полей if (field === 'totalQuantity') { aValue = parseFloat(aValue); bValue = parseFloat(bValue); } // Для стоимости if (field === 'totalCost') { aValue = parseFloat(a.totalCost); bValue = parseFloat(b.totalCost); } // Для дат if (field === 'lastUpdated') { aValue = parseDate(a.lastUpdated); bValue = parseDate(b.lastUpdated); } // Для статуса if (field === 'indicator') { const order = { 'danger': 0, 'warning': 1, 'success': 2 }; aValue = order[a.indicator?.class] || 3; bValue = order[b.indicator?.class] || 3; } if (direction === 'asc') { return aValue > bValue ? 1 : aValue < bValue ? -1 : 0; } else { return aValue < bValue ? 1 : aValue > bValue ? -1 : 0; } }); } // Функция фильтрации async function filterData(searchText) { if (!searchText.trim()) { filteredData = [...data]; } else { const searchLower = searchText.toLowerCase(); filteredData = data.filter(item => item.title.toLowerCase().includes(searchLower) || item.category.toLowerCase().includes(searchLower) || item.placement.toLowerCase().includes(searchLower) || item.totalQuantity.toString().includes(searchLower) || item.totalCost.toString().includes(searchLower) || (item.indicator?.text && item.indicator.text.toLowerCase().includes(searchLower)) ); } currentPage = 1; sortData(currentSort.field, currentSort.direction); await initializePagination(); } // Инициализация сортировки по заголовкам document.querySelectorAll('#toolboxItemsTable th[data-sort]').forEach(th => { th.addEventListener('click', async () => { const field = th.dataset.sort; if (currentSort.field === field) { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.field = field; currentSort.direction = 'asc'; } // Обновляем иконки сортировки document.querySelectorAll('#toolboxItemsTable th i').forEach(icon => { icon.className = 'bi bi-arrow-down-up'; }); const currentIcon = th.querySelector('i'); currentIcon.className = currentSort.direction === 'asc' ? 'bi bi-arrow-up' : 'bi bi-arrow-down'; sortData(currentSort.field, currentSort.direction); await initializePagination(); }); }); // Инициализация поиска const searchInput = document.getElementById('toolboxSearch'); searchInput.addEventListener('input', async (e) => { await filterData(e.target.value); }); // Инициализация кнопки сброса фильтра document.getElementById('resetFilter').addEventListener('click', async () => { searchInput.value = ''; await filterData(''); }); // Начальная инициализация sortData(currentSort.field, currentSort.direction); await initializePagination(); } async function getToolkitStocks(toolkitId) { const userId = userData.id; const allToolboxes = accessData.view_all_toolboxes; const resp = await apiRequest('/toolkit/', { toolkitId, userId, allToolboxes }); return resp.data; } // Функция показа модального окна с деталями инструмента async function showToolkitDetailsModal(item) { const modalId = 'toolkitDetailsModal'; let modal = document.getElementById(modalId); if (modal) { modal.remove(); } modal = document.createElement('div'); modal.className = 'modal fade'; modal.id = modalId; modal.tabIndex = -1; let images = []; if (typeof item.image === 'string') { images = item.image ? [item.image, ...(item.images || [])] : [item.image]; } else { images = item.image ? [item.image.main, ...(item.image.additional || [])] : [item.image.main];; } let imagesDiv = ''; if (images.length > 1) { const carouselId = `carousel-${item.id}`; imagesDiv = `
`; } else { imagesDiv = `
${item.title}
`; } const description = item.description || item.toolkitData?.description || 'Нет описания'; const specifications = item.toolkitData?.specifications || item.specifications || {}; const external_link = item.external_link || item.toolkitData?.external_link || null; const category_desc = item.categoryData?.description || item.category_desc || ''; const toolkitStocks = await getToolkitStocks(item.id); modal.innerHTML = ` `; document.body.appendChild(modal); const bsModal = new bootstrap.Modal(modal); bsModal.show(); modal.addEventListener('hidden.bs.modal', () => { modal.remove(); }); // После создания модального окна добавьте инициализацию lightbox lightbox.option({ 'resizeDuration': 200, 'wrapAround': true, 'albumLabel': "Изображение %1 из %2", 'fadeDuration': 300, 'imageFadeDuration': 300 }); } // Функция показа модального окна для операций async function showOperationModal(operation, selectedItem) { const modalId = 'operationModal'; let modal = document.getElementById(modalId); if (modal) { modal.remove(); } modal = document.createElement('div'); modal.className = 'modal fade'; modal.id = modalId; modal.tabIndex = -1; const operationTitles = { 'return': 'Возврат инструмента', 'writeoff': 'Списание инструмента', 'get': 'Получение инструмента' }; // Определяем максимальное доступное количество modal.innerHTML = ` `; document.body.appendChild(modal); const bsModal = new bootstrap.Modal(modal); bsModal.show(); // Валидация ввода количества const quantityInput = document.getElementById('operationQuantity'); quantityInput.addEventListener('change', function () { let value = parseInt(this.value); if (value > selectedItem.totalQuantity) { this.value = selectedItem.totalQuantity; } else if (value < 1) { this.value = 1; } }); document.getElementById('confirmOperation').addEventListener('click', async (e) => { const btn = e.currentTarget; const btnText = btn.innerHTML; // Блокируем кнопку + ставим спиннер btn.disabled = true; btn.innerHTML = ` Обработка... `; const quantity = parseInt(document.getElementById('operationQuantity').value); const comment = document.getElementById('operationComment').value; // Проверка максимального количества для операций списания и получения if ((operation === 'writeoff' || operation === 'get') && quantity > selectedItem.totalQuantity) { showError(`Максимально доступное количество: ${selectedItem.totalQuantity}`); resetButton(); return; } if (comment === '') { showError('Введите обоснование'); resetButton(); return; } const success = await actionRequest(operation, quantity, comment, selectedItem); if (success) { bsModal.hide(); showInfo(`Запрос на ${operationTitles[operation]} успешно создан`, 'success'); await loadToolboxContent(selectedItem.toolboxId); } else { showError('Ошибка выполнения операции'); resetButton(); } function resetButton() { btn.disabled = false; btn.innerHTML = btnText; } function showError(message) { document.getElementById('operationError').classList.remove('d-none'); document.getElementById('operationErrorMessage').textContent = message; } }); modal.addEventListener('hidden.bs.modal', () => { modal.remove(); }); } async function actionRequest(operation, quantity, comment, selectedItem) { const action = { operation, quantity, comment, selectedItem }; const sendData = { userData, accessData, action }; const resp = await apiRequest('/stocks/action', sendData); if (resp.status == 'ok') { return true } else { return false } } function formatKey(key) { const keyMap = { 'id': 'ID', 'title': 'Название', 'description': 'Описание', 'owner_id': 'ID владельца', 'monitoring': 'Мониторинг', 'created_at': 'Дата создания', 'updated_at': 'Дата обновления' }; return keyMap[key] || key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' '); } function formatValue(value) { if (value === null || value === undefined) return '—'; if (typeof value === 'boolean') return value ? 'Да' : 'Нет'; if (typeof value === 'object') return JSON.stringify(value); return value.toString(); } document.addEventListener('DOMContentLoaded', async () => { await getCookieData(); if (!accessData || !userData) { console.warn('Access data or user data not found'); return; } prepareTabs(); }); window.openTab = openTab;