заказы

This commit is contained in:
2025-12-21 03:45:02 +03:00
parent 83ecf65abf
commit 546c70cbcd
15 changed files with 862 additions and 38 deletions
+48 -26
View File
@@ -1,15 +1,17 @@
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, Request
from fastapi.responses import RedirectResponse
from db.handlers.access import AccessLevelHandler
from db.handlers.categories import CategoryHandler
from db.handlers.orders import OrdersHandler
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
from .records import router as records
from .orders import router as orders
router = APIRouter()
@@ -19,6 +21,7 @@ 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.include_router(records, prefix="/records", tags=["records"])
router.include_router(orders, prefix="/orders", tags=["orders"])
@router.get("/")
@@ -52,6 +55,21 @@ async def post_requests(
from db.handlers.toolkit import ToolkitHandler
from db.handlers.user import UserHandler
def getDates(data: dict):
startDate = data.get("startDate")
if isinstance(startDate, str):
startDate = datetime.strptime(startDate, "%Y-%m-%d").date()
if startDate is None:
startDate = datetime.now().date() - timedelta(days=30)
endDate = data.get("endDate")
if isinstance(endDate, str):
endDate = datetime.strptime(endDate, "%Y-%m-%d").date()
if endDate is None:
endDate = datetime.now().date()
if startDate > endDate:
startDate, endDate = endDate, startDate
return startDate, endDate
reqData = {
"tab": request_data.get("body").get("tabId"),
}
@@ -105,18 +123,7 @@ async def post_requests(
"categories": categories,
}
case "jurnal_toolkits":
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
startDate, endDate = getDates(request_data.get("body"))
jurnal_toolkits = await StocksRecordsHandler.getLogs(startDate, endDate)
if isinstance(jurnal_toolkits, list):
@@ -147,18 +154,7 @@ async def post_requests(
"endDate": endDate.strftime("%Y-%m-%d"),
}
case "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
startDate, endDate = getDates(request_data.get("body"))
jurnal_service = await ServiceRecordsHandler.getLogs(startDate, endDate)
if isinstance(jurnal_service, list):
@@ -199,7 +195,33 @@ async def post_requests(
"users": users,
"accessLevels": accessLevels,
}
# logger.info(resultData)
case "orders":
fullAccess = reqData["accessData"].get("view_all_toolboxes", False)
startDate, endDate = getDates(request_data.get("body"))
if fullAccess:
result = await OrdersHandler.get_all(startDate, endDate)
if "errorMessage" in result.keys():
resultData["message"] = result["errorMessage"]
else:
resultData["status"] = "ok"
if "orders" in result.keys():
resultData["data"]["orders"] = result["orders"]
else:
userId = reqData.get("userData").get("id")
result = await OrdersHandler.get_all_by_consumer(
userId, startDate, endDate
)
if "errorMessage" in result.keys():
resultData["message"] = result["errorMessage"]
else:
resultData["status"] = "ok"
if "orders" in result.keys():
resultData["data"]["orders"] = result["orders"]
if resultData["status"] == "ok":
resultData["data"]["startDate"] = startDate.strftime("%Y-%m-%d")
resultData["data"]["endDate"] = endDate.strftime("%Y-%m-%d")
resultData["data"]["users"] = await UserHandler.getAll()
resultData["data"]["fullAccess"] = fullAccess
case _:
pass
return resultData
Binary file not shown.
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
from fastapi import APIRouter, Depends
from db.handlers.orders import OrdersHandler
from utils import requestDict, logger
router = APIRouter()
@router.get("/")
async def get_requests():
return await OrdersHandler.countNew()
@router.post("/")
async def post_requests(
reqData: dict = Depends(requestDict),
):
logger.info("Обновление заказа")
response = {"status": "error"}
orderId = reqData.get("body").get("orderId")
userId = reqData.get("body").get("userId")
status = reqData.get("body").get("status")
comment = reqData.get("body").get("comment")
if (orderId is not None and userId is not None) and (
status is not None or comment is not None
):
result = await OrdersHandler.update(int(orderId), userId, status, comment)
if "errorMessage" in result.keys():
response["message"] = result["errorMessage"]
else:
response["status"] = "ok"
if "orders" in result.keys():
response["data"] = result["orders"]
return response
@router.post("/new", summary="Добавление нового заказа")
async def add_order(reqData: dict = Depends(requestDict)):
logger.info(f"Добавление нового заказа")
userId = reqData.get("body").get("userId")
customer_comment = reqData.get("body").get("customer_comment")
return await OrdersHandler.new(userId, customer_comment)
+1
View File
@@ -2,6 +2,7 @@ body {
background-image: url("../images/background.svg");
background-repeat: repeat;
background-size: 512px auto;
background-attachment: fixed;
}
.loader-bg {
+621 -7
View File
@@ -89,7 +89,11 @@ async function checkActiveUser() {
}
}
async function openTab(event, tabId) {
async function openTab(event, tabId, autoLoad = false) {
const activeTab = loadFromStorage('tab');
if (activeTab && activeTab.tabId === tabId && !autoLoad) {
return;
}
// Убираем активный класс со всех вкладок и кнопок
document.querySelectorAll('.tab-nav-btn').forEach(btn => {
btn.classList.remove('active');
@@ -136,6 +140,12 @@ function prepareTabs() {
};
}
tabsData['orders'] = {
title: 'Заказы',
icon: 'bi-basket',
description: 'Управление заказами'
};
if (accessData.view_requests) {
tabsData['jurnal_toolkits'] = {
title: 'Журнал перемещений',
@@ -170,11 +180,11 @@ function prepareTabs() {
<div id="mainTabsNavWrapper">
<nav class="nav nav-pills gap-2" id="mainTabsNav" role="tablist">
${Object.entries(tabsData).map(([tabId, tabData], index) => `
<button class="nav-link tab-nav-btn d-flex flex-column align-items-center justify-content-center py-3 px-2"
id="${tabId}-tab"
role="tab"
onclick="openTab(event, '${tabId}')"
style="min-width: 120px; transition: all 0.3s ease;">
<button class="nav-link tab-nav-btn d-flex flex-column align-items-center justify-content-center py-3 px-2 position-relative"
id="${tabId}-tab"
role="tab"
onclick="openTab(event, '${tabId}')"
style="min-width: 120px; transition: all 0.3s ease;">
<i class="${tabData.icon} nav-icon fs-3 mb-2 text-muted" style="transition: all 0.3s ease;"></i>
<span class="nav-title fw-medium" style="font-size: 0.9rem;">${tabData.title}</span>
<div class="nav-indicator"></div>
@@ -240,7 +250,7 @@ function prepareTabs() {
const activeTabId = activeTabData.tabId;
const tabBtn = document.getElementById(`${activeTabId}-tab`);
if (tabBtn) {
openTab({ currentTarget: tabBtn }, activeTabId);
openTab({ currentTarget: tabBtn }, activeTabId, true);
}
}
}
@@ -280,6 +290,9 @@ function fillTab(tabId, tabData) {
case 'toolkits':
renderToolkitsTab(tabId, tabData.toolkits, tabData.categories);
break;
case 'orders':
renderOrdersTab(tabId, tabData);
break;
case 'jurnal_toolkits':
renderJurnalToolkitsTab(tabId, tabData);
break;
@@ -5986,6 +5999,578 @@ function renderRequestsTab(tabId, tabData) {
renderRequestsTable();
}
function renderOrdersTab(tabId, tabData) {
const tabContent = document.getElementById(`${tabId}-tab-content`);
const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
const { orders, users, startDate, endDate, fullAccess } = tabData;
// Создаем мапу пользователей
const userMap = {};
users.forEach(user => {
userMap[user.id] = user.username;
});
// const ordersStatuses = {
// }
// Сохраненные фильтры
const savedFilters = loadFromStorage(tabId);
let currentFilters = {
customer: savedFilters?.customer || 'all',
status: savedFilters?.status || 'all',
search: savedFilters?.search || ''
};
// Хранилище измененных данных
const changedOrders = {};
const statusesList = []
const statusesMap = {
'new': 'Новый',
'working': 'В работе',
'complete': 'Выполнен',
'cancelled': 'Отменен',
}
// Оригинальные данные заказов (для сравнения)
const originalOrders = {};
orders.forEach(o => {
originalOrders[o.id] = {
status: o.status,
executor_comment: o.executor_comment || ''
};
if (!statusesList.includes(o.status)) {
statusesList.push(o.status)
}
});
// Рендерим дополнительный контейнер с фильтрами и кнопками
tabOptionalContent.innerHTML = `
<div class="row align-items-center mb-1">
<!-- Левая часть с фильтрами -->
<div class="col-12 col-md-6 mb-2 mb-md-0">
<div class="row align-items-center mb-2">
<!-- Поиск -->
<div class="col-12 col-md-9">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-search"></i>
</span>
<input type="text" class="form-control"
id="${tabId}-search-filter"
placeholder="Поиск по таблице..."
value="${currentFilters.search}">
</div>
</div>
<!-- Кнопка сброса -->
<div class="col-12 col-md-3">
<button class="btn btn-outline-secondary" id="${tabId}-filter-reset-btn">
<i class="bi bi-x-circle me-1"></i>Сброс
</button>
</div>
</div>
<!-- Фильтр по статусу -->
<div class="row g-2">
<div class="col-12 col-md-5">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-list-check"></i>
</span>
<select class="form-select" id="${tabId}-status-filter">
<option value="all">Все статусы</option>
${statusesList.map(status => `<option value="${status}">${statusesMap[status] || status}</option>`).join('')}
</select>
</div>
</div>
${!fullAccess ? `
<div class="col-12 col-md-7">
<button class="btn btn-primary" id="${tabId}-new-order-btn">
<i class="bi bi-plus-circle me-1"></i>Оформить заказ
</button>
</div>
` : `
<div class="col-12 col-md-7">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-person"></i>
</span>
<select class="form-select" id="${tabId}-customer-filter">
<option value="all">Все заказчики</option>
${[...new Set(orders.map(o => o.customer_id))].map(customerId => `
<option value="${customerId}">${userMap[customerId] || `Пользователь ${customerId}`}</option>
`).join('')}
</select>
</div>
</div>
` }
</div>
</div>
<div class="col-12 col-md-2">
<button class="btn btn-outline-primary" id="${tabId}-date-update-btn">
Обновить список заказов
</button>
</div>
<!-- Правая часть с датами и кнопками -->
<div class="col-12 col-md-4">
<div class="d-flex flex-wrap gap-2 justify-content-md-end">
<div class="input-group date">
<span class="input-group-text" style="width: 170px;">
<i class="bi bi-calendar me-1"></i>Дата начала:
</span>
<input type="date" class="form-control" id="${tabId}-date-from">
</div>
<div class="input-group date">
<span class="input-group-text" style="width: 170px;">
<i class="bi bi-calendar me-1"></i>Дата окончания:
</span>
<input type="date" class="form-control" id="${tabId}-date-to">
</div>
</div>
</div>
</div>
`;
// Модальное окно для нового заказа
if (!fullAccess) {
document.body.insertAdjacentHTML('beforeend', `
<div class="modal fade" id="${tabId}-new-order-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Оформление нового заказа</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="${tabId}-order-description" class="form-label">Описание заказа</label>
<textarea class="form-control" id="${tabId}-order-description"
rows="4" placeholder="Опишите, что вам нужно..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="${tabId}-order-submit-btn">Заказать</button>
</div>
</div>
</div>
</div>
`);
}
// Инициализация фильтров
if (fullAccess) {
document.getElementById(`${tabId}-customer-filter`).value = currentFilters.customer;
}
document.getElementById(`${tabId}-status-filter`).value = currentFilters.status;
document.getElementById(`${tabId}-search-filter`).value = currentFilters.search;
// Обработчики событий
const filterResetBtn = document.getElementById(`${tabId}-filter-reset-btn`);
filterResetBtn.addEventListener('click', () => {
currentFilters = {
customer: 'all',
status: 'all',
search: ''
};
if (fullAccess) {
document.getElementById(`${tabId}-customer-filter`).value = currentFilters.customer;
}
document.getElementById(`${tabId}-status-filter`).value = currentFilters.status;
document.getElementById(`${tabId}-search-filter`).value = currentFilters.search;
saveToStorage(tabId, currentFilters);
renderOrdersTable();
});
// Дата фильтры
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;
if (newStartDate && newEndDate) {
tabContent.innerHTML = `
<div class="alert alert-info m-4" role="alert">
<i class="bi bi-info-circle me-2"></i>
Загрузка данных...
</div>
`;
const cookiesData = { userData, accessData };
const newPeriodData = await apiRequest('/', {
tabId: tabId,
startDate: newStartDate,
endDate: newEndDate,
cookiesData
});
if (newPeriodData.status === 'ok') {
renderOrdersTab(tabId, {
...tabData,
...newPeriodData.data,
startDate: newStartDate,
endDate: newEndDate,
fullAccess
});
await checkNewOrders();
}
}
});
// Кнопка нового заказа
if (!fullAccess) {
const newOrderBtn = document.getElementById(`${tabId}-new-order-btn`);
newOrderBtn.addEventListener('click', () => {
const modal = new bootstrap.Modal(document.getElementById(`${tabId}-new-order-modal`));
modal.show();
});
// Отправка нового заказа
const orderSubmitBtn = document.getElementById(`${tabId}-order-submit-btn`);
orderSubmitBtn.onclick = async () => {
if (orderSubmitBtn.disabled) return;
orderSubmitBtn.disabled = true;
const description = document.getElementById(`${tabId}-order-description`).value;
if (!description.trim()) {
showInfo('Пожалуйста, введите описание заказа', 'warning');
orderSubmitBtn.disabled = false;
return;
}
try {
const result = await apiRequest('/orders/new', {
userId: userData.id,
customer_comment: description
});
if (result.status === 'ok') {
const modal = bootstrap.Modal.getInstance(
document.getElementById(`${tabId}-new-order-modal`)
);
modal.hide();
document.getElementById(`${tabId}-order-description`).value = '';
refreshDateBtn.click();
} else {
showInfo(result.message || 'Ошибка при создании заказа', 'danger');
}
} finally {
orderSubmitBtn.disabled = false;
}
};
}
if (orders.length === 0) {
tabContent.innerHTML = `
<div class="alert alert-info m-4" role="alert">
<i class="bi bi-info-circle me-2"></i>
Нет заказов за период ${startDate} - ${endDate}
</div>
`;
return;
}
// Рендерим таблицу
tabContent.innerHTML = `
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0" id="${tabId}-orders-table">
<thead class="table-light">
<tr>
<th width="150">Заказчик</th>
<th>Описание заказа</th>
<th width="120">Статус</th>
<th width="150">Исполнитель</th>
<th>Комментарий исполнителя</th>
${fullAccess ? '<th width="30">⚙️</th>' : ''}
</tr>
</thead>
<tbody id="${tabId}-orders-body">
<!-- Заказы будут вставлены здесь -->
</tbody>
</table>
</div>
<div class="text-center p-3 border-top" id="${tabId}-no-orders" style="display: none;">
<i class="bi bi-inbox fs-1 text-muted"></i>
<p class="text-muted mt-2">Нет заказов по выбранным фильтрам</p>
</div>
</div>
</div>
`;
// Функция фильтрации
function filterOrders() {
let filtered = orders;
// Фильтр по заказчику
if (fullAccess && currentFilters.customer !== 'all') {
filtered = filtered.filter(o => o.customer_id == currentFilters.customer);
}
// Фильтр по статусу
if (currentFilters.status !== 'all') {
filtered = filtered.filter(o => o.status === currentFilters.status);
}
// Поиск
if (currentFilters.search.trim()) {
const searchTerm = currentFilters.search.toLowerCase();
filtered = filtered.filter(o => {
return (
(userMap[o.customer_id] || '').toLowerCase().includes(searchTerm) ||
o.customer_comment.toLowerCase().includes(searchTerm) ||
(userMap[o.executor_id] || '').toLowerCase().includes(searchTerm) ||
(o.executor_comment || '').toLowerCase().includes(searchTerm) ||
o.status.toLowerCase().includes(searchTerm)
);
});
}
return filtered;
}
// Функция рендеринга таблицы
function renderOrdersTable() {
const tbody = document.getElementById(`${tabId}-orders-body`);
const noOrdersDiv = document.getElementById(`${tabId}-no-orders`);
const filteredOrders = filterOrders();
if (filteredOrders.length === 0) {
tbody.innerHTML = '';
noOrdersDiv.style.display = 'block';
return;
}
noOrdersDiv.style.display = 'none';
tbody.innerHTML = filteredOrders.map(order => {
// Определяем класс для статуса
const statusClass = {
new: 'warning',
working: 'primary',
complete: 'success',
cancelled: 'danger'
}[order.status] || 'secondary';
// Определяем русское название статуса
const statusText = {
new: 'Новый',
working: 'В работе',
complete: 'Выполнен',
cancelled: 'Отменен'
}[order.status] || order.status;
// Рендерим статус
let statusCell;
if (fullAccess && (order.status === 'new' || order.status === 'working')) {
statusCell = `
<select class="form-select form-select-sm order-status"
data-order-id="${order.id}"
style="min-width: 120px;">
<option value="new" ${order.status === 'new' ? 'selected' : ''} disabled>Новый</option>
<option value="working" ${order.status === 'working' ? 'selected' : ''}>В работе</option>
<option value="complete" ${order.status === 'complete' ? 'selected' : ''}>Выполнен</option>
<option value="cancelled" ${order.status === 'cancelled' ? 'selected' : ''}>Отменен</option>
</select>
`;
} else {
statusCell = `<span class="badge bg-${statusClass}">${statusText}</span>`;
}
// Рендерим комментарий исполнителя
let commentCell;
if (fullAccess && (order.status === 'new' || order.status === 'working')) {
commentCell = `
<textarea class="form-control form-control-sm executor-comment"
data-order-id="${order.id}"
rows="2"
placeholder="Введите комментарий...">${order.executor_comment || ''}</textarea>
`;
} else {
commentCell = `<small class="text-muted">${order.executor_comment || 'Нет комментария'}</small>`;
}
return `
<tr data-order-id="${order.id}">
<td class="align-middle">
${userMap[order.customer_id] || `Пользователь ${order.customer_id}`}<br>
<small class="text-muted">${order.created_at}</small>
</td>
<td class="align-middle"><small>${order.customer_comment}</small></td>
<td class="align-middle">
${statusCell}<br>
<small class="text-muted">${order.updated_at}</small>
</td>
<td class="align-middle">${order.executor_id ? (userMap[order.executor_id] || `Пользователь ${order.executor_id}`) : '-'}</td>
<td class="align-middle">${commentCell}</td>
${fullAccess ? `
<td class="align-middle text-center">
${fullAccess && (order.status === 'new' || order.status === 'working') ? `
<button class="btn btn-sm btn-success save-row-btn"
data-order-id="${order.id}"
disabled>
<i class="bi bi-save"></i>
</button>
` : '☑️'}
</td>
`: ''}
</tr>
`;
}).join('');
function updateRowState(orderId) {
const original = originalOrders[orderId];
const current = changedOrders[orderId];
const hasChanges =
current &&
(
(current.status !== undefined && current.status !== original.status) ||
(current.executor_comment !== undefined && current.executor_comment !== original.executor_comment)
);
const saveBtn = document.querySelector(
`.save-row-btn[data-order-id="${orderId}"]`
);
if (saveBtn) {
saveBtn.disabled = !hasChanges;
}
}
// Добавляем обработчики изменений
if (fullAccess) {
document.querySelectorAll(`.order-status`).forEach(select => {
select.addEventListener('change', function () {
const orderId = this.dataset.orderId;
changedOrders[orderId] = {
...changedOrders[orderId],
status: this.value
};
updateRowState(orderId);
});
});
document.querySelectorAll(`.executor-comment`).forEach(textarea => {
textarea.addEventListener('input', function () {
const orderId = this.dataset.orderId;
changedOrders[orderId] = {
...changedOrders[orderId],
executor_comment: this.value
};
updateRowState(orderId);
});
});
document.querySelectorAll('.save-row-btn').forEach(btn => {
btn.addEventListener('click', async function () {
const orderId = this.dataset.orderId;
const original = originalOrders[orderId];
const current = changedOrders[orderId];
if (!current) return;
// Формируем diff
const payload = { orderId, userId: userData.id };
if (current.status !== undefined && current.status !== original.status) {
payload.status = current.status;
}
if (
current.executor_comment !== undefined &&
current.executor_comment !== original.executor_comment
) {
payload.comment = current.executor_comment;
}
if (Object.keys(payload).length === 2) return;
this.disabled = true;
const result = await apiRequest('/orders/', payload);
if (result.status === 'ok') {
// Обновляем оригинал
originalOrders[orderId] = {
status: payload.status ?? original.status,
executor_comment: payload.comment ?? original.executor_comment
};
delete changedOrders[orderId];
if (payload.status && original.status === 'new') {
await checkNewOrders();
}
this.innerHTML = '<i class="bi bi-check-circle"></i>';
setTimeout(() => {
this.innerHTML = '<i class="bi bi-save"></i>';
}, 1500);
if (originalOrders[orderId].status === 'complete' || originalOrders[orderId].status === 'cancelled') {
const foundOrder = orders.find(order => order.id === Number(orderId));
if (foundOrder) {
foundOrder.executor_id = userData.id;
foundOrder.executor_comment = originalOrders[orderId].executor_comment;
foundOrder.status = current.status;
}
renderOrdersTable();
}
} else {
showInfo('Ошибка сохранения', 'danger');
this.disabled = false;
}
});
});
}
}
// Обработчики фильтров
if (fullAccess) {
document.getElementById(`${tabId}-customer-filter`).addEventListener('change', function () {
currentFilters.customer = this.value;
saveToStorage(tabId, currentFilters);
renderOrdersTable();
});
}
document.getElementById(`${tabId}-status-filter`).addEventListener('change', function () {
currentFilters.status = this.value;
saveToStorage(tabId, currentFilters);
renderOrdersTable();
});
document.getElementById(`${tabId}-search-filter`).addEventListener('input', function () {
currentFilters.search = this.value;
saveToStorage(tabId, currentFilters);
renderOrdersTable();
});
// Первоначальный рендеринг
renderOrdersTable();
}
function renderJurnalToolkitsTab(tabId, tabData) {
const tabContent = document.getElementById(`${tabId}-tab-content`);
const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
@@ -7446,6 +8031,34 @@ function renderUsersTab(tabId, tabData) {
renderUsersCards();
}
async function checkNewOrders() {
const result = await apiRequest('/orders/', {}, 'GET');
if (result && result.orders > 0) {
const ordersTabBtn = document.getElementById('orders-tab');
// Удаляем старый бейдж, если есть
const oldBadge = ordersTabBtn.querySelector('.orders-badge');
if (oldBadge) oldBadge.remove();
const newOrdersBadge = document.createElement('span');
newOrdersBadge.className =
'badge rounded-pill bg-danger position-absolute orders-badge';
newOrdersBadge.textContent = result.orders;
// Позиция: правый верх
newOrdersBadge.style.top = '6px';
newOrdersBadge.style.right = '8px';
ordersTabBtn.appendChild(newOrdersBadge);
}
if (result && result.orders == 0) {
const ordersTabBtn = document.getElementById('orders-tab');
const oldBadge = ordersTabBtn.querySelector('.orders-badge');
if (oldBadge) oldBadge.remove();
}
}
document.addEventListener('DOMContentLoaded', async () => {
await getCookieData();
@@ -7456,6 +8069,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
prepareTabs();
await checkNewOrders();
});
window.openTab = openTab;