diff --git a/api/routers/__init__.py b/api/routers/__init__.py index a2bfbfb..29ecfc6 100644 --- a/api/routers/__init__.py +++ b/api/routers/__init__.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, Depends, Request +from fastapi.responses import RedirectResponse from db.handlers.categories import CategoryHandler from utils import render, requestDict, logger @@ -18,7 +19,24 @@ router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"]) @router.get("/") async def main_page(request: Request): - return await render(request) + cookies = request.cookies + checkList = ["toolbox_user", "toolbox_access"] + if all(key in cookies for key in checkList): + return await render(request) + else: + for key in checkList: + if key in cookies: + deleteCookie = key + break + else: + deleteCookie = None + + if deleteCookie: + response = RedirectResponse(url="/user/login", status_code=302) + response.set_cookie(deleteCookie, "", expires=0) + return response + else: + return RedirectResponse(url="/user/login", status_code=302) @router.post("/") diff --git a/api/routers/__pycache__/__init__.cpython-313.pyc b/api/routers/__pycache__/__init__.cpython-313.pyc index fb4c8ca..61f5e61 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 86c890f..8470eef 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 fd8bf7d..a1bd68c 100644 --- a/api/routers/toolkit.py +++ b/api/routers/toolkit.py @@ -9,6 +9,28 @@ from utils import requestDict, logger router = APIRouter() +def handleResult(result: dict, response: dict) -> dict: + if "errorMessage" in result.keys(): + response["message"] = result["errorMessage"] + else: + response["status"] = "ok" + return response + + +@router.get("/", summary="Получение инструмента") +async def get_toolkit(reqData: dict = Depends(requestDict)): + logger.info(f"Получение инструмента") + response = {"status": "error"} + toolkitId = reqData.get("query").get("toolkitId") + if toolkitId: + toolkit = await ToolkitHandler.get(int(toolkitId)) + if toolkit: + # logger.info(toolkit) + response["status"] = "ok" + response["data"] = toolkit + return response + + @router.post("/", summary="Запрос остатка инструмента") async def toolkit_request( reqData: dict = Depends(requestDict), @@ -16,7 +38,6 @@ async def toolkit_request( response = {"status": "error", "data": {}} toolkitId = reqData.get("body").get("toolkitId") logger.info(f"Получение запроса остатка инструмента #{toolkitId}") - # logger.info(request_data) stocks = await StockHandler.getByToolkitId(toolkitId) if not stocks: return response @@ -100,3 +121,74 @@ async def categories_batch(reqData: dict = Depends(requestDict)): if success: response["status"] = "ok" return response + + +@router.get("/categories", summary="Получение категорий") +async def get_categories(): + logger.info(f"Получение категорий") + response = {"status": "error"} + categories = await CategoryHandler.getAll() + if categories: + categoriesDict = { + category["id"]: { + "id": category["id"], + "title": category["title"], + "description": category["description"], + } + for category in categories + } + response["status"] = "ok" + response["data"] = categoriesDict + return response + + +@router.post("/hide", summary="Скрытие инструмента") +async def hide_toolkit(reqData: dict = Depends(requestDict)): + + logger.info(f"Скрытие/отображение инструмента") + response = {"status": "error"} + toolkitId = int(reqData.get("body").get("toolkitId")) + userId = reqData.get("body").get("userId") + hidden = reqData.get("body").get("hidden") + result = await ToolkitHandler.hideToolkit(userId, toolkitId, hidden) + response = handleResult(result, response) + return response + + +@router.post("/manage", summary="Управление инструментами") +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") + toolkitData = reqData.get("body").get("formData") + if "category_id" in toolkitData: + toolkitData["category_id"] = int(toolkitData.get("category_id")) + if "image" in toolkitData: + if ( + not toolkitData.get("image").get("main") + or toolkitData.get("image").get("main") == "" + ): + if len(toolkitData.get("image").get("additional")) == 0: + toolkitData.pop("image") + match action: + case "create": + toolkit = await ToolkitHandler.add(toolkitData, userId) + response = handleResult(toolkit, response) + case "copy": + toolkitData.pop("id") + toolkit = await ToolkitHandler.add(toolkitData, userId) + response = handleResult(toolkit, response) + case "update": + toolkit = await ToolkitHandler.edit(userId, **toolkitData) + response = handleResult(toolkit, response) + case "delete": + toolkit = await ToolkitHandler.delete(toolkitData.get("id"), userId) + response = handleResult(toolkit, response) + case _: + pass + logger.info( + f"Управление инструментами ({action}) прошло {'успешно' if response.get('status') == 'ok' else 'неуспешно'}" + ) + return response diff --git a/api/static/images/tools/plastina_2_1765571207912.png b/api/static/images/tools/plastina_2_1765571207912.png new file mode 100644 index 0000000..4824077 Binary files /dev/null and b/api/static/images/tools/plastina_2_1765571207912.png differ diff --git a/api/static/images/tools/test2_1765562971246.png b/api/static/images/tools/test2_1765562971246.png new file mode 100644 index 0000000..005168c Binary files /dev/null and b/api/static/images/tools/test2_1765562971246.png differ diff --git a/api/static/images/tools/test_1765562719945.png b/api/static/images/tools/test_1765562719945.png new file mode 100644 index 0000000..a33524b Binary files /dev/null and b/api/static/images/tools/test_1765562719945.png differ diff --git a/api/static/js/api.js b/api/static/js/api.js index b4360b1..b827918 100644 --- a/api/static/js/api.js +++ b/api/static/js/api.js @@ -1,14 +1,27 @@ // api.js export async function apiRequest(url, payload = {}, method = 'POST') { - const res = await fetch(url, { + method = method.toUpperCase(); + + let finalUrl = url; + let options = { method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, - body: JSON.stringify(payload), credentials: 'same-origin' - }); + }; + + // --- Если GET → добавляем payload в URL --- + if (method === 'GET') { + const params = new URLSearchParams(payload); + finalUrl = `${url}?${params.toString()}`; + } else { + // --- Для остальных методов → отправляем body --- + options.body = JSON.stringify(payload); + } + + const res = await fetch(finalUrl, options); if (!res.ok) { const text = await res.text(); diff --git a/api/static/js/cookies.js b/api/static/js/cookies.js index f335f1a..f5e774b 100644 --- a/api/static/js/cookies.js +++ b/api/static/js/cookies.js @@ -23,16 +23,26 @@ export async function setCookie(name, value, days = 180) { } } - const secure = true; // TODO включить после тестов - const sameSite = 'Lax'; - const expires = new Date(Date.now() + days * 864e5).toUTCString(); + const encodedName = encodeURIComponent(name); - let cookie = `${encodeURIComponent(name)}=${cookieValue}; expires=${expires}; path=/`; - if (secure) cookie += '; Secure'; - if (sameSite) cookie += `; SameSite=${sameSite}`; + // ---------- 1. Пытаемся установить безопасную куку ---------- + let secureCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; Secure; SameSite=Lax`; + document.cookie = secureCookie; - document.cookie = cookie; + // ---------- 2. Проверяем, записалась ли она ---------- + const isSet = document.cookie.split('; ') + .some(c => c.startsWith(`${encodedName}=`)); + + if (isSet) { + return true; // безопасная кука успешно установлена + } + + // ---------- 3. Фолбэк: ставим обычную (без Secure) ---------- + let normalCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; SameSite=Lax`; + document.cookie = normalCookie; + + return false; // безопасную куку установить не удалось } export async function getCookie(name) { diff --git a/api/static/js/index.js b/api/static/js/index.js index 2cbb16b..ffa0ede 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -6,6 +6,64 @@ let accessData; let userData; let currentToolboxData = null; +// Список предопределенных характеристик с разделами +const predefinedSpecs = { + "Универсальные": [ + "Диаметр", + "Длина", + "Черновая", + "Чистовая", + "Материал инструмента", + "Покрытие (TiN, TiAlN, AlTiN)", + "Тип хвостовика", + "Назначение", + "По стали", + "По нержавейке", + "По алюминию", + "Твёрдый сплав", + "HSS" + ], + + "Фрезеровка": [ + "Кол-во перьев", + "Тип фрезы (концевая, торцевая, черновая)", + "Угол спирали", + "Геометрия зубьев" + ], + + "Токарка": [ + "Пластины", + "Форма пластины (C, D, V, W, T)", + "Радиус", + "Наружная", + "Внутренняя", + "Резьбовая", + "Шаг", + "Тип державки", + "Направление (правое/левое)", + "Система крепления" + ], + + "Сверла": [ + "Угол заточки (118°, 135°)", + "Тип хвостовика (цилиндрический, Морзе)", + "Глубокое сверление" + ], + + "Инструмент для ЧПУ": [ + "Тип инструмента (фреза, сверло, развертка, гравёр, фасочник)", + "Тип обработки (2D, 3D, контурная, карманная)", + "Ступенчатая геометрия", + "Тип крепления (ER, Weldon, HSK, BT, ISO)", + "Максимальные обороты", + "Максимальная подача", + "Допуск биения", + "Тип спирали (правосторонняя, левосторонняя)", + "Длина режущей части", + "Рабочая часть" + ] +}; + async function getCookieData() { accessData = await getCookie('toolbox_access'); userData = await getCookie('toolbox_user'); @@ -1062,22 +1120,45 @@ async function manageCategory(categoriesList) { function renderToolkitsTab(tabId, toolsList, categoriesArray) { const tabContent = document.getElementById(`${tabId}-tab-content`); const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`); + const hiddenToolCount = toolsList.filter(tool => tool.hidden).length; if (accessData.tools_creation) { tabOptionalContent.innerHTML = ` -
+
- +
+ + +
+
+ + +
`; const manageCategoryBtn = document.getElementById('manageCategoryBtn'); manageCategoryBtn.addEventListener('click', () => manageCategory(categoriesArray)); - } + const addToolBtn = document.getElementById('addToolBtn'); + addToolBtn.addEventListener('click', () => manageToolkit()); + } else { + tabOptionalContent.innerHTML = ` +
+
+
+ + +
+
+
+ `; + } let categoriesData = {}; categoriesArray.forEach(cat => { @@ -1146,11 +1227,14 @@ function renderToolkitsTab(tabId, toolsList, categoriesArray) { // Функция для рендеринга карточек -function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', categoryFilter = 'all') { +function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', categoryFilter = 'all', showHiddenTools = false) { const container = document.getElementById(`${tabId}-cards-container`); // Фильтруем инструменты const filteredTools = tools.filter(tool => { + // Показываем скрытые инструменты, если флаг установлен + const showHidden = showHiddenTools || !tool.hidden; + // Фильтр по категории const categoryMatch = categoryFilter === 'all' || tool.category_id == categoryFilter; @@ -1159,7 +1243,7 @@ function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', catego (tool.title && tool.title.toLowerCase().includes(filterText.toLowerCase())) || (tool.description && tool.description.toLowerCase().includes(filterText.toLowerCase())); - return categoryMatch && searchMatch; + return categoryMatch && searchMatch && showHidden; }); // Рендерим карточки @@ -1192,6 +1276,11 @@ function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', catego ${categoryName} + ${tool.hidden ? ` + + + + ` : ''}
${tool.title || 'Без названия'}
@@ -1217,8 +1306,7 @@ function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', catego 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); + await showToolkitDetailsModal(toolId); }); }); } @@ -1227,6 +1315,12 @@ function renderToolkitCards(tabId, tools, categoriesMap, filterText = '', catego function setupFilters(tabId, tools, categoriesMap) { const searchInput = document.getElementById(`${tabId}-search-input`); const filterButtons = document.querySelectorAll(`#${tabId}-tab-content .filter-btn`); + const showHiddenToolsCheckbox = document.getElementById(`showHiddenTools`); + + showHiddenToolsCheckbox.addEventListener('change', () => { + renderToolkitCards(tabId, tools, categoriesMap, currentFilter.search, currentFilter.category, showHiddenToolsCheckbox.checked); + }); + // Текущие значения фильтров let currentFilter = { @@ -1326,7 +1420,7 @@ function addToolbox(editData = null) {
-
+