diff --git a/api/routers/__init__.py b/api/routers/__init__.py index 3f4d6f5..141bcbf 100644 --- a/api/routers/__init__.py +++ b/api/routers/__init__.py @@ -114,6 +114,9 @@ async def post_requests( if isinstance(endDate, str): endDate = datetime.strptime(endDate, "%Y-%m-%d").date() + if startDate > endDate: + startDate, endDate = endDate, startDate + jurnal_toolkits = await StocksRecordsHandler.getLogs(startDate, endDate) if isinstance(jurnal_toolkits, list): if len(jurnal_toolkits) == 0: @@ -143,10 +146,42 @@ async def post_requests( "endDate": endDate.strftime("%Y-%m-%d"), } case "jurnal_service": - jurnal_service = await ServiceRecordsHandler.get() - if jurnal_service: - resultData["status"] = "ok" - resultData["data"] = jurnal_service + startDate = request_data.get("body").get( + "startDate", date.today() - timedelta(days=7) + ) + if isinstance(startDate, str): + startDate = datetime.strptime(startDate, "%Y-%m-%d").date() + + endDate = request_data.get("body").get("endDate", date.today()) + if isinstance(endDate, str): + endDate = datetime.strptime(endDate, "%Y-%m-%d").date() + + if startDate > endDate: + startDate, endDate = endDate, startDate + + jurnal_service = await ServiceRecordsHandler.getLogs(startDate, endDate) + if isinstance(jurnal_service, list): + if len(jurnal_service) == 0: + resultData["status"] = "ok" + resultData["data"] = { + "requests": [], + "users": [], + "categories": [], + "startDate": startDate.strftime("%Y-%m-%d"), + "endDate": endDate.strftime("%Y-%m-%d"), + } + else: + users = await UserHandler.getAll() + categories = await CategoryHandler.getAll() + resultData["status"] = "ok" + resultData["data"] = { + "requests": jurnal_service, + "users": users, + "categories": categories, + "startDate": startDate.strftime("%Y-%m-%d"), + "endDate": endDate.strftime("%Y-%m-%d"), + } + # logger.info(resultData.get("data")) case "users": users = await UserHandler.getAll() if users: diff --git a/api/routers/__pycache__/__init__.cpython-313.pyc b/api/routers/__pycache__/__init__.cpython-313.pyc index 8fe3f59..8f8d2ea 100644 Binary files a/api/routers/__pycache__/__init__.cpython-313.pyc and b/api/routers/__pycache__/__init__.cpython-313.pyc differ diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc index 775dbcb..67dc3d1 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 7185226..9ba3b3b 100644 --- a/api/routers/toolkit.py +++ b/api/routers/toolkit.py @@ -160,7 +160,7 @@ async def manage_toolkit(reqData: dict = Depends(requestDict)): logger.info(f"Управление инструментами") response = {"status": "error"} action = reqData.get("body").get("action") - userId = reqData.get("body").get("UserId") + userId = reqData.get("body").get("userId") toolkitData = reqData.get("body").get("formData") if "category_id" in toolkitData: toolkitData["category_id"] = int(toolkitData.get("category_id")) diff --git a/api/static/js/index.js b/api/static/js/index.js index 328c724..ce3770b 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -264,7 +264,7 @@ function fillTab(tabId, tabData) { renderJurnalToolkitsTab(tabId, tabData); break; case 'jurnal_service': - renderSimpleTab(tabId, tabData, 'Сервисный журнал'); + renderJurnalServicesTab(tabId, tabData); break; case 'users': renderSimpleTab(tabId, tabData, 'Пользователи системы'); @@ -1454,10 +1454,16 @@ function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', catego }).join(''); const cards = container.querySelectorAll('.toolkit-card'); + let activeModal = null; + cards.forEach(card => { card.addEventListener('click', async event => { + if (activeModal) return; + const toolId = event.currentTarget.dataset.toolid; + activeModal = true; await showToolkitDetailsModal(toolId); + activeModal = null; }); }); } @@ -3503,11 +3509,15 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) { }); // Добавляем обработчики для изображений + let activeModal = null; document.querySelectorAll('.toolkit-image-link').forEach(link => { link.addEventListener('click', async (e) => { + if (activeModal) return; e.preventDefault(); const itemId = e.currentTarget.dataset.id; + activeModal = true; await showToolkitDetailsModal(itemId); + activeModal = null; }); }); } @@ -5371,7 +5381,7 @@ function renderRequestsTab(tabId, tabData) { ${request.action} - ${request.created_at} + ${request.created_at} ${userMap[request.init_user_id] || `Пользователь ${request.init_user_id}`} ${request.source_toolbox_id ? (toolboxMap[request.source_toolbox_id] || `Склад ${request.source_toolbox_id}`) : '-'} @@ -5851,8 +5861,8 @@ function renderJurnalToolkitsTab(tabId, tabData) { ${request.action} ${statusBadge} - ${userMap[request.init_user_id] || `Пользователь ${request.init_user_id}`}
${request.created_at} - ${userMap[request.decision_user_id] || `Пользователь ${request.decision_user_id}`}
${request.decided_at} + ${userMap[request.init_user_id] || `Пользователь ${request.init_user_id}`}
${request.created_at} + ${userMap[request.decision_user_id] || `Пользователь ${request.decision_user_id}`}
${request.decided_at} ${request.source_toolbox_id ? (toolboxMap[request.source_toolbox_id] || `Склад ${request.source_toolbox_id}`) : '-'} ${request.target_toolbox_id ? (toolboxMap[request.target_toolbox_id] || `Склад ${request.target_toolbox_id}`) : '-'} ${request.toolkit_id ? (toolkitMap[request.toolkit_id] || `Инструмент ${request.toolkit_id}`) : '-'} @@ -5889,6 +5899,334 @@ function renderJurnalToolkitsTab(tabId, tabData) { // Первоначальный рендеринг таблицы renderRequestsTable(); } + +function renderJurnalServicesTab(tabId, tabData) { + const tabContent = document.getElementById(`${tabId}-tab-content`); + const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`); + + const { requests, users, categories, startDate, endDate } = tabData; + + if (requests.length === 0) { + tabContent.innerHTML = ` + + `; + return; + } + + // Собираем списки для фильтров + const initUsers = [...new Set(requests.map(r => r.user_id))].filter(id => id !== null); + const userMap = {}; + users.forEach(user => { + userMap[user.id] = user.username; + }); + + const categoriesMap = {}; + categories.forEach(cat => { + categoriesMap[cat.id] = { title: cat.title, description: cat.description }; + }); + + // Собираем типы действий (ключи из details) + const actionTypes = [...new Set(requests.map(r => { + const details = r.details; + return Object.keys(details)[0]; // Берем первый ключ как тип действия + }))]; + + const savedFilters = loadFromStorage(tabId); + // Фильтры + let currentFilters = { + user: savedFilters?.user || 'all', + action: savedFilters?.action || 'all' + }; + + // Рендерим дополнительный контейнер с фильтрами + tabOptionalContent.innerHTML = ` +
+ +
+
+
+
+ + + + +
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+ + +
+
+
+ + Дата начала: + + +
+
+ + Дата окончания: + + +
+
+ +
+
+
+
+ `; + + const filterResetBtn = document.getElementById(`${tabId}-filter-reset-btn`); + filterResetBtn.addEventListener('click', () => { + currentFilters = { + user: 'all', + action: 'all' + }; + document.getElementById(`${tabId}-user-filter`).value = currentFilters.user; + document.getElementById(`${tabId}-action-filter`).value = currentFilters.action; + saveToStorage(tabId, currentFilters); + renderServicesTable(); + }); + + const startDateInput = document.getElementById(`${tabId}-date-from`); + const endDateInput = document.getElementById(`${tabId}-date-to`); + startDateInput.value = startDate; + endDateInput.value = endDate; + + const refreshDateBtn = document.getElementById(`${tabId}-date-update-btn`); + refreshDateBtn.addEventListener('click', async () => { + const newStartDate = startDateInput.value; + const newEndDate = endDateInput.value; + const newDateRequestData = { + tabId: tabId, + startDate: newStartDate, + endDate: newEndDate + }; + if (newStartDate && newEndDate) { + tabContent.innerHTML = ` + + `; + const newPeriodData = await apiRequest('/', newDateRequestData); + if (newPeriodData.status == 'ok') { + renderJurnalServicesTab(tabId, newPeriodData.data); + } + } + }); + + // Рендерим основной контейнер с таблицей сервисных событий + tabContent.innerHTML = ` +
+
+
+ + + + + + + + + + + + +
ДатаПользовательДействиеДетали
+
+ +
+
+ `; + + // Функция для фильтрации событий + function filterServices() { + let filtered = requests; + + // Фильтр по пользователю + if (currentFilters.user !== 'all') { + if (currentFilters.user === 'system') { + filtered = filtered.filter(r => r.user_id === null); + } else { + filtered = filtered.filter(r => r.user_id == currentFilters.user); + } + } + + // Фильтр по типу действия + if (currentFilters.action !== 'all') { + filtered = filtered.filter(r => { + const details = r.details; + return Object.keys(details)[0] === currentFilters.action; + }); + } + + return filtered; + } + + // Функция для рендеринга строк таблицы + function renderServicesTable() { + const tbody = document.getElementById(`${tabId}-services-body`); + const noServicesDiv = document.getElementById(`${tabId}-no-services`); + const filteredServices = filterServices(); + + if (filteredServices.length === 0) { + tbody.innerHTML = ''; + noServicesDiv.style.display = 'block'; + return; + } + + noServicesDiv.style.display = 'none'; + + tbody.innerHTML = filteredServices.map(service => { + const actionType = Object.keys(service.details)[0]; + const actionData = service.details[actionType]; + + // Определяем пользователя + let userName = 'Система'; + if (service.user_id) { + userName = userMap[service.user_id] || `Пользователь ${service.user_id}`; + } + + // Форматируем детали в зависимости от типа действия + let detailsHtml = ''; + + if (actionType === 'Авторизован пользователь') { + // Для авторизации + detailsHtml = ` +
${actionData}
+ `; + } else if (actionType.includes('Добавлен') || actionType.includes('Обновлен') || actionType.includes('Добавлена')) { + // Для добавления/обновления сущностей + const entityName = actionData.title || actionData.username || actionData.login || ''; + detailsHtml = ` +
${entityName}
+ ${actionData.description ? `
${actionData.description}
` : ''} + ${actionData.id ? `
ID: ${actionData.id}
` : ''} + `; + + // Для инструментов добавляем дополнительные поля + if (actionData.specifications && Object.keys(actionData.specifications).length > 0) { + detailsHtml += `
Характеристики:
`; + detailsHtml += `
`; + for (const [key, value] of Object.entries(actionData.specifications)) { + detailsHtml += `
${key}: ${value}
`; + } + detailsHtml += `
`; + } + + if (actionData.external_link) { + detailsHtml += `
Ссылка: ${actionData.external_link}
`; + } + + if (actionData.quantity_min || actionData.quantity_min_extra) { + detailsHtml += `
Мониторинг остатков:
`; + if (actionData.quantity_min && actionData.quantity_min_extra) { + detailsHtml += `
Минимальное количество: ${actionData.quantity_min}
`; + detailsHtml += `
Минимальное критическое количество: ${actionData.quantity_min_extra}
`; + } else if (actionData.quantity_min && !actionData.quantity_min_extra) { + detailsHtml += `
Минимальное количество: ${actionData.quantity_min}
`; + } else if (actionData.quantity_min_extra && !actionData.quantity_min) { + detailsHtml += `
Минимальное критическое количество: ${actionData.quantity_min_extra}
`; + } + } + + if (actionData.category_id) { + detailsHtml += `
Категория: ${categoriesMap[actionData.category_id].title} [${categoriesMap[actionData.category_id].description}]
`; + } + + if (actionData.image) { + detailsHtml += `
Изображения:
`; + detailsHtml += `
Основное:
`; + detailsHtml += `
Основное изображение инструмента`; + if (actionData.image.additional) { + detailsHtml += `
Дополнительные:
`; + detailsHtml += `
`; + actionData.image.additional.forEach(img => { + detailsHtml += `
Дополнительное изображение инструмента
`; + }); + detailsHtml += `
`; + } + detailsHtml += `
`; + } + } + + return ` + + + ${service.created_at} + + ${userName} + + ${actionType} + + + ${detailsHtml} + + + `; + }).join(''); + } + + // Назначаем обработчики событий для фильтров + document.getElementById(`${tabId}-user-filter`).addEventListener('change', function () { + currentFilters.user = this.value; + saveToStorage(tabId, currentFilters); + renderServicesTable(); + }); + + document.getElementById(`${tabId}-action-filter`).addEventListener('change', function () { + currentFilters.action = this.value; + saveToStorage(tabId, currentFilters); + renderServicesTable(); + }); + + // Устанавливаем сохраненные значения фильтров + if (savedFilters) { + document.getElementById(`${tabId}-user-filter`).value = currentFilters.user; + document.getElementById(`${tabId}-action-filter`).value = currentFilters.action; + } + + // Первоначальный рендеринг таблицы + renderServicesTable(); +} + document.addEventListener('DOMContentLoaded', async () => { await getCookieData(); diff --git a/db/handlers/__pycache__/records.cpython-313.pyc b/db/handlers/__pycache__/records.cpython-313.pyc index dd10b1e..efdd21b 100644 Binary files a/db/handlers/__pycache__/records.cpython-313.pyc and b/db/handlers/__pycache__/records.cpython-313.pyc differ diff --git a/db/handlers/__pycache__/toolkit.cpython-313.pyc b/db/handlers/__pycache__/toolkit.cpython-313.pyc index 047be3c..01a5e17 100644 Binary files a/db/handlers/__pycache__/toolkit.cpython-313.pyc and b/db/handlers/__pycache__/toolkit.cpython-313.pyc differ diff --git a/db/handlers/records.py b/db/handlers/records.py index a4129e4..190836f 100644 --- a/db/handlers/records.py +++ b/db/handlers/records.py @@ -287,3 +287,35 @@ class ServiceRecordsHandler: except Exception as e: logger.error(f"Ошибка получения записей: {str(e)}") return False + + async def getLogs(startDate: date, endDate: date): + from db import CRUD + + try: + start_dt = datetime.combine(startDate, time.min) + end_dt = datetime.combine(endDate, time.max) + + query = ( + select(ServicesRecords) + .where( + ServicesRecords.created_at.between(start_dt, end_dt), + ) + .order_by(ServicesRecords.created_at.desc()) + ) + + logger.debug("Получение записей за период %s - %s", startDate, endDate) + + records = await CRUD.read(query, True) + + logger.debug( + "%d записей за период %s - %s успешно получены", + len(records), + startDate, + endDate, + ) + + return [record.toDict() for record in records] + + except Exception: + logger.exception("Ошибка получения записей") + return [] diff --git a/db/handlers/toolkit.py b/db/handlers/toolkit.py index ab5b8cd..56b71b1 100644 --- a/db/handlers/toolkit.py +++ b/db/handlers/toolkit.py @@ -160,7 +160,7 @@ class ToolkitHandler: f"Инструмент {editedToolkit.title} успешно обновлен, изменены данные: {kwargs.keys()}" ) await ServiceRecordsHandler.add( - user_id, {f"Обновлен инструмент {toolkit.title}": editedToolkit.toDict()} + user_id, {f"Обновлен инструмент": editedToolkit.toDict()} ) return editedToolkit.toDict()