diff --git a/api/__pycache__/__init__.cpython-313.pyc b/api/__pycache__/__init__.cpython-313.pyc index d59b572..30102f8 100644 Binary files a/api/__pycache__/__init__.cpython-313.pyc and b/api/__pycache__/__init__.cpython-313.pyc differ diff --git a/api/routers/__init__.py b/api/routers/__init__.py index e539751..7885478 100644 --- a/api/routers/__init__.py +++ b/api/routers/__init__.py @@ -4,12 +4,16 @@ from db.handlers.categories import CategoryHandler from utils import render, requestDict, logger from .user import router as user from .stocks import router as stocks +from .toolbox import router as toolbox +from .toolkit import router as toolkit router = APIRouter() router.include_router(user, prefix="/user", tags=["user"]) router.include_router(stocks, prefix="/stocks", tags=["stocks"]) +router.include_router(toolbox, prefix="/toolbox", tags=["toolbox"]) +router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"]) @router.get("/") diff --git a/api/routers/__pycache__/__init__.cpython-313.pyc b/api/routers/__pycache__/__init__.cpython-313.pyc index 210f87a..d0e02ed 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__/stocks.cpython-313.pyc b/api/routers/__pycache__/stocks.cpython-313.pyc index a82c2e0..e75b551 100644 Binary files a/api/routers/__pycache__/stocks.cpython-313.pyc and b/api/routers/__pycache__/stocks.cpython-313.pyc differ diff --git a/api/routers/__pycache__/toolbox.cpython-313.pyc b/api/routers/__pycache__/toolbox.cpython-313.pyc new file mode 100644 index 0000000..eb7f388 Binary files /dev/null and b/api/routers/__pycache__/toolbox.cpython-313.pyc differ diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc new file mode 100644 index 0000000..f6e933b Binary files /dev/null and b/api/routers/__pycache__/toolkit.cpython-313.pyc differ diff --git a/api/routers/stocks.py b/api/routers/stocks.py index 1214e36..8a9d990 100644 --- a/api/routers/stocks.py +++ b/api/routers/stocks.py @@ -11,16 +11,17 @@ from utils import requestDict, logger router = APIRouter() -@router.post("/") +@router.post("/", summary="Получение инструментов для тулбокса") async def post_requests( request_data: dict = Depends(requestDict), ): toolboxId = request_data.get("body").get("toolboxId") logger.info(f"Получение инструментов для тулбокса {toolboxId}") - response = {"status": "error", "data": {}} + response = {"status": "error", "data": []} stocksData = await StockHandler.getByToolboxId(toolboxId) if not stocksData: + response["status"] = "ok" return response toolkitsIds = set(stock["toolkit_id"] for stock in stocksData) toolkitsData = await ToolkitHandler.getSeveral(list(toolkitsIds)) @@ -107,42 +108,3 @@ async def post_requests( if result: resonse["status"] = "ok" return resonse - - -@router.post("/toolkit", summary="Запрос остатка инструмента") -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") - stocks = await StockHandler.getByToolkitId(toolkitId) - if not stocks: - return response - userId = request_data.get("body").get("userId") - allToolboxes = request_data.get("body").get("allToolboxes") - toolboxes = ( - await ToolboxHandler.getByOwner(userId) - if not allToolboxes - else await ToolboxHandler.getAll() - ) - if not toolboxes: - return response - toolboxesTitles = {toolbox["id"]: toolbox["title"] for toolbox in toolboxes} - stocksData = {"count": 0, "toolboxes": {}} - for stock in stocks: - toolboxTitle = toolboxesTitles.get(stock["toolbox_id"], None) - if not toolboxTitle: - continue - stocksData["count"] += stock["quantity"] - if toolboxTitle not in stocksData["toolboxes"]: - stocksData["toolboxes"][toolboxTitle] = { - "count": stock["quantity"], - "placement": stock["placement"], - } - else: - stocksData["toolboxes"][toolboxTitle]["count"] += stock["quantity"] - response["status"] = "ok" - response["data"] = stocksData - return response diff --git a/api/routers/toolbox.py b/api/routers/toolbox.py new file mode 100644 index 0000000..c930989 --- /dev/null +++ b/api/routers/toolbox.py @@ -0,0 +1,38 @@ +from fastapi import APIRouter, Depends +from db.handlers.stock import StockHandler +from db.handlers.toolbox import ToolboxHandler +from utils import requestDict, logger + + +router = APIRouter() + + +@router.post("/", summary="Добавление ящика") +async def add_toolbox(reqDict=Depends(requestDict)): + logger.info(f"Добавление ящика") + response = {"status": "error"} + userId = reqDict.get("body").get("userId") + toolboxData = reqDict.get("body").get("toolboxData") + result = await ToolboxHandler.add(toolboxData, userId) + if result: + response["status"] = "ok" + logger.info(response) + return response + + +@router.delete("/", summary="Удаление ящика") +async def delete_toolbox(reqDict=Depends(requestDict)): + toolboxId = reqDict.get("body").get("toolboxId") + logger.info(f"Удаление ящика #{toolboxId}") + response = {"status": "error"} + stocksData = await StockHandler.getByToolboxId(toolboxId, False) + if stocksData: + response["message"] = ( + "Через этот склад были проведены операции, удаление невозможно" + ) + return response + userId = reqDict.get("body").get("userId") + result = await ToolboxHandler.delete(toolboxId, userId) + if result: + response["status"] = "ok" + return response diff --git a/api/routers/toolkit.py b/api/routers/toolkit.py new file mode 100644 index 0000000..b3cf6e5 --- /dev/null +++ b/api/routers/toolkit.py @@ -0,0 +1,46 @@ +from fastapi import APIRouter, Depends +from db.handlers.stock import StockHandler +from db.handlers.toolbox import ToolboxHandler +from utils import requestDict, logger + + +router = APIRouter() + + +@router.post("/", summary="Запрос остатка инструмента") +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") + stocks = await StockHandler.getByToolkitId(toolkitId) + if not stocks: + return response + userId = request_data.get("body").get("userId") + allToolboxes = request_data.get("body").get("allToolboxes") + toolboxes = ( + await ToolboxHandler.getByOwner(userId) + if not allToolboxes + else await ToolboxHandler.getAll() + ) + if not toolboxes: + return response + toolboxesTitles = {toolbox["id"]: toolbox["title"] for toolbox in toolboxes} + stocksData = {"count": 0, "toolboxes": {}} + for stock in stocks: + toolboxTitle = toolboxesTitles.get(stock["toolbox_id"], None) + if not toolboxTitle: + continue + stocksData["count"] += stock["quantity"] + if toolboxTitle not in stocksData["toolboxes"]: + stocksData["toolboxes"][toolboxTitle] = { + "count": stock["quantity"], + "placement": stock["placement"], + } + else: + stocksData["toolboxes"][toolboxTitle]["count"] += stock["quantity"] + response["status"] = "ok" + response["data"] = stocksData + return response diff --git a/api/static/css/index.css b/api/static/css/index.css index 13675d3..d314205 100644 --- a/api/static/css/index.css +++ b/api/static/css/index.css @@ -342,4 +342,118 @@ tr:hover .action-buttons { .toolkit-card-img { height: 150px; } +} + +/* Стили для модального окна добавления склада */ +.modal-content { + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +.modal-header { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 12px 12px 0 0; + padding: 1.25rem 1.5rem; +} + +.modal-title { + color: #495057; + font-weight: 600; +} + +.modal-body { + padding: 1.5rem; +} + +.modal-footer { + border-top: 1px solid rgba(0, 0, 0, 0.08); + padding: 1rem 1.5rem; +} + +/* Стили для обязательных полей */ +.form-label.required::after { + content: " *"; + color: #dc3545; +} + +/* Кастомные стили для переключателя */ +.form-switch .form-check-input { + width: 3em; + height: 1.5em; + cursor: pointer; + transition: all 0.3s ease; +} + +.form-switch .form-check-input:checked { + background-color: #0d6efd; + border-color: #0d6efd; +} + +.form-switch .form-check-input:focus { + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +.form-switch .form-check-label { + font-weight: 500; + cursor: pointer; +} + +/* Стили для текстовых полей в фокусе */ +.form-control:focus { + border-color: #86b7fe; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + transition: all 0.3s ease; +} + +/* Стили для валидации */ +.form-control.is-valid { + border-color: #198754; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +.form-control.is-invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +/* Стили для текста подсказки */ +.form-text { + color: #6c757d; + font-size: 0.875em; + margin-top: 0.25rem; +} + +/* Стили для кнопок в модальном окне */ +.modal-footer .btn { + min-width: 100px; + font-weight: 500; +} + +/* Адаптивность для мобильных */ +@media (max-width: 576px) { + .modal-dialog { + margin: 0.5rem; + } + + .modal-content { + border-radius: 8px; + } + + .modal-body { + padding: 1rem; + } + + .modal-footer { + padding: 0.75rem 1rem; + } } \ No newline at end of file diff --git a/api/static/js/api.js b/api/static/js/api.js index 0cabd47..b4360b1 100644 --- a/api/static/js/api.js +++ b/api/static/js/api.js @@ -1,8 +1,6 @@ // api.js export async function apiRequest(url, payload = {}, method = 'POST') { - const finalUrl = new URL(url, window.location.origin).toString(); - - const res = await fetch(finalUrl, { + const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json', diff --git a/api/static/js/index.js b/api/static/js/index.js index 9d3ae75..8a2290f 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -1,5 +1,6 @@ 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; @@ -112,9 +113,9 @@ function prepareTabs() {