This commit is contained in:
2025-12-21 19:02:40 +03:00
parent 546c70cbcd
commit 599158f8e1
10 changed files with 639 additions and 27 deletions
+396 -16
View File
@@ -1,6 +1,6 @@
import { getCookie } from '/static/js/cookies.js';
import { apiRequest } from '/static/js/api.js';
import { showInfo } from '/static/js//toast.js';
import { showInfo } from '/static/js/toast.js';
let accessData;
let userData;
@@ -3836,14 +3836,16 @@ async function getToolkitStocks(toolkitId) {
return resp.data;
}
// Функция показа модального окна с деталями инструмента
// Функция показа модального окна с деталями инструмента
async function showToolkitDetailsModal(toolkitId) {
const modalId = 'toolkitDetailsModal';
let modal = document.getElementById(modalId);
if (modal) {
modal.hide();
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) {
modalInstance.hide();
}
modal.remove();
}
@@ -3930,9 +3932,12 @@ async function showToolkitDetailsModal(toolkitId) {
` : '<div class="col-md-4"></div>';
}
// Переменная для хранения данных об остатках (будет загружена при раскрытии аккордеона)
// Переменные для хранения данных
let toolkitStocksData = null;
let isStocksLoading = false;
let compatibilityData = null;
let isCompatibilityLoading = false;
let allToolkitsData = null;
// Форматирование даты комментария
let commentDateInfo = '';
@@ -4028,6 +4033,8 @@ async function showToolkitDetailsModal(toolkitId) {
</div>
</div>
${toolkitData.external_link ? `
<div class="mt-3">
<a href="${toolkitData.external_link}" target="_blank" class="btn btn-sm btn-outline-primary">
@@ -4037,6 +4044,45 @@ async function showToolkitDetailsModal(toolkitId) {
` : ''}
</div>
</div>
<!-- Аккордеон для совместимости инструментов -->
<div class="accordion mt-3" id="compatibilityAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="compatibilityHeading">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#compatibilityCollapse"
aria-expanded="false" aria-controls="compatibilityCollapse">
<i class="bi bi-link-45deg me-2"></i>Совместимые инструменты
</button>
</h2>
<div id="compatibilityCollapse" class="accordion-collapse collapse"
aria-labelledby="compatibilityHeading" data-bs-parent="#compatibilityAccordion">
<div class="accordion-body">
<div id="compatibilityLoading" class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
<p class="mt-2 text-muted">Загрузка данных о совместимости...</p>
</div>
<div id="compatibilityContent" class="d-none">
<!-- Содержимое будет загружено динамически -->
</div>
<div id="compatibilityError" class="d-none text-center text-danger py-3 mb-3">
<i class="bi bi-exclamation-triangle me-2"></i>
<span>Не удалось загрузить данные о совместимости</span>
</div>
${accessData.tools_edit ? `
<button class="btn btn-sm btn-outline-success ms-auto me-2"
id="addCompatibilityBtn"
style="display: none;">
<i class="bi bi-plus-circle me-1"></i>Добавить совместимый инструмент
</button>
` : ''}
</div>
</div>
</div>
</div>
<!-- Секция комментариев -->
<div class="border-top pt-3 mt-3">
@@ -4140,8 +4186,6 @@ async function showToolkitDetailsModal(toolkitId) {
// Показываем результат
if (response.status === 'ok') {
// Успешное отображение
// Обновляем список инструментов
if (typeof uploadTab === 'function') {
await uploadTab('toolkits');
@@ -4370,26 +4414,362 @@ async function showToolkitDetailsModal(toolkitId) {
}
};
// Обработчик события раскрытия аккордеона
// Функция для загрузки данных о совместимости
const loadCompatibilityData = async () => {
if (isCompatibilityLoading) return;
const compatibilityLoading = modal.querySelector('#compatibilityLoading');
const compatibilityContent = modal.querySelector('#compatibilityContent');
const compatibilityError = modal.querySelector('#compatibilityError');
const addCompatibilityBtn = modal.querySelector('#addCompatibilityBtn');
try {
isCompatibilityLoading = true;
// Показываем спиннер, скрываем контент и ошибку
compatibilityLoading.classList.remove('d-none');
compatibilityContent.classList.add('d-none');
compatibilityError.classList.add('d-none');
// Загружаем данные о совместимости
const response = await apiRequest(`/toolkit/compatibility?toolkitId=${toolkitData.id}`, {}, 'GET');
if (response.status === 'ok') {
compatibilityData = response.data;
// Формируем HTML для совместимости
let compatibilityHtml = '';
if (Object.keys(compatibilityData.records || {}).length > 0) {
compatibilityHtml = `
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Инструмент</th>
<th>Категория</th>
<th>Комментарий</th>
${accessData.tools_edit ? '<th width="50">Действия</th>' : ''}
</tr>
</thead>
<tbody>
${Object.entries(compatibilityData.records || {}).map(([recordId, compatibleToolkitId]) => {
const compatibleToolkit = compatibilityData.toolkits[compatibleToolkitId];
const compatibleCategory = categories[compatibleToolkit?.category_id];
return `
<tr data-record-id="${recordId}" data-toolkit-id="${compatibleToolkitId}">
<td>
<strong>${compatibleToolkit?.title || 'Неизвестный инструмент'}</strong>
${compatibleToolkit ? `
<button class="btn btn-sm btn-link p-0 ms-1 view-toolkit-btn"
data-toolkit-id="${compatibleToolkitId}"
title="Просмотреть детали">
<i class="bi bi-eye"></i>
</button>
` : ''}
</td>
<td>${compatibleCategory?.title || 'Неизвестно'}</td>
<td><small class="text-muted">${compatibleToolkit?.comment_text || 'Нет комментария'}</small></td>
${accessData.tools_edit ? `
<td>
<button class="btn btn-sm btn-outline-danger delete-compatibility-btn"
data-record-id="${recordId}"
data-toolkit-id="${compatibleToolkitId}"
title="Удалить совместимость">
<i class="bi bi-trash"></i>
</button>
</td>
` : ''}
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
} else {
compatibilityHtml = `
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle me-2"></i>
Нет данных о совместимых инструментах
</div>
`;
}
// Вставляем HTML и показываем контент
compatibilityContent.innerHTML = compatibilityHtml;
compatibilityContent.classList.remove('d-none');
// Показываем кнопку добавления, если есть права
if (accessData.tools_edit) {
addCompatibilityBtn.style.display = 'inline-block';
}
// Добавляем обработчики для кнопок просмотра инструмента
compatibilityContent.querySelectorAll('.view-toolkit-btn').forEach(button => {
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) {
modalInstance.hide();
}
modal.addEventListener('hidden.bs.modal', async () => {
await showToolkitDetailsModal(compatibleToolkitId);
}, { once: true });
});
});
// Добавляем обработчики для кнопок удаления
compatibilityContent.querySelectorAll('.delete-compatibility-btn').forEach(button => {
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const recordId = e.currentTarget.dataset.recordId;
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
if (confirm('Вы уверены, что хотите удалить эту связь совместимости?')) {
await deleteCompatibility(recordId, compatibleToolkitId);
}
});
});
} else {
throw new Error(response.message || 'Ошибка загрузки данных о совместимости');
}
} catch (error) {
console.error('Ошибка при загрузке данных о совместимости:', error);
compatibilityError.classList.remove('d-none');
} finally {
compatibilityLoading.classList.add('d-none');
isCompatibilityLoading = false;
}
};
// Функция для удаления совместимости
const deleteCompatibility = async (recordId, compatibleToolkitId) => {
try {
const response = await apiRequest('/toolkit/compatibility', {
action: 'delete',
userId: userData.id,
data: {
toolkitId: toolkitData.id,
compatibleToolkitId: compatibleToolkitId
}
}, 'POST');
if (response.status === 'ok') {
showInfo('Связь совместимости успешно удалена', 'success');
// Обновляем данные
compatibilityData = null;
await loadCompatibilityData();
} else {
throw new Error(response.message || 'Ошибка удаления связи');
}
} catch (error) {
console.error('Ошибка при удалении совместимости:', error);
showInfo(error.message || 'Произошла ошибка при удалении связи', 'danger');
}
};
// Функция для добавления совместимости
const addCompatibility = async (compatibleToolkitId) => {
try {
const response = await apiRequest('/toolkit/compatibility', {
action: 'add',
userId: userData.id,
data: {
toolkitId: toolkitData.id,
compatibleToolkitId: compatibleToolkitId
}
}, 'POST');
if (response.status === 'ok') {
showInfo('Связь совместимости успешно добавлена', 'success');
// Обновляем данные
compatibilityData = null;
await loadCompatibilityData();
// Закрываем модальное окно добавления
const addModal = bootstrap.Modal.getInstance(document.getElementById(`${toolkitData.id}-add-compatibility-modal`));
if (addModal) addModal.hide();
} else {
throw new Error(response.message || 'Ошибка добавления связи');
}
} catch (error) {
console.error('Ошибка при добавлении совместимости:', error);
showInfo(error.message || 'Произошла ошибка при добавлении связи', 'danger');
}
};
// Функция для показа модального окна добавления совместимости
const showAddCompatibilityModal = async () => {
// Загружаем все инструменты, если еще не загружены
if (!allToolkitsData) {
try {
const response = await apiRequest('/toolkit/all', {}, 'GET');
if (response.status === 'ok') {
allToolkitsData = response.data;
}
} catch (error) {
console.error('Ошибка загрузки списка инструментов:', error);
showInfo('Не удалось загрузить список инструментов', 'danger');
return;
}
}
// Создаем модальное окно
const addModalId = `${toolkitData.id}-add-compatibility-modal`;
let addModal = document.getElementById(addModalId);
if (addModal) {
addModal.remove();
}
addModal = document.createElement('div');
addModal.className = 'modal fade';
addModal.id = addModalId;
addModal.tabIndex = -1;
// Фильтруем инструменты: исключаем текущий и уже совместимые
const compatibleIds = Object.values(compatibilityData?.records || {});
const filteredToolkits = Object.values(allToolkitsData || {}).filter(toolkit =>
toolkit.id !== toolkitData.id && !compatibleIds.includes(toolkit.id)
);
addModal.innerHTML = `
<div class="modal-dialog modal-lg">
<div class="modal-content modal-content-green">
<div class="modal-header">
<h5 class="modal-title">Добавить совместимый инструмент</h5>
<button type="button"
class="btn-close btn-close"
data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<input type="text" class="form-control" id="compatibilitySearch"
placeholder="Поиск инструмента по названию...">
</div>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm table-hover">
<thead>
<tr>
<th width="40"></th>
<th>Название</th>
<th>Категория</th>
<th>Описание</th>
</tr>
</thead>
<tbody id="compatibilityToolkitsList">
${filteredToolkits.map(toolkit => {
const category = categories[toolkit.category_id];
return `
<tr data-toolkit-id="${toolkit.id}"
data-toolkit-title="${toolkit.title.toLowerCase()}">
<td>
<button class="btn btn-sm btn-outline-primary select-compatibility-btn"
data-toolkit-id="${toolkit.id}">
<i class="bi bi-plus"></i>
</button>
</td>
<td><strong>${toolkit.title}</strong></td>
<td>${category?.title || 'Неизвестно'}</td>
<td><small class="text-muted">${toolkit.description || 'Нет описания'}</small></td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
${filteredToolkits.length === 0 ? `
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Нет доступных инструментов для добавления
</div>
` : ''}
</div>
<div class="modal-footer bg-success text-white">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
Отмена
</button>
</div>
</div>
</div>
`;
document.body.appendChild(addModal);
const bsAddModal = new bootstrap.Modal(addModal);
bsAddModal.show();
// Функция поиска инструментов
const searchInput = addModal.querySelector('#compatibilitySearch');
const toolkitsList = addModal.querySelector('#compatibilityToolkitsList');
const rows = toolkitsList.querySelectorAll('tr');
searchInput.addEventListener('input', function () {
const searchTerm = this.value.toLowerCase();
rows.forEach(row => {
const title = row.dataset.toolkitTitle;
const isVisible = title.includes(searchTerm);
row.style.display = isVisible ? '' : 'none';
});
});
// Обработчики для кнопок выбора
addModal.querySelectorAll('.select-compatibility-btn').forEach(button => {
button.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
await addCompatibility(compatibleToolkitId);
});
});
// Очистка при закрытии
addModal.addEventListener('hidden.bs.modal', () => {
addModal.remove();
});
};
// Обработчик события раскрытия аккордеона остатков
const stocksCollapse = modal.querySelector('#stocksCollapse');
stocksCollapse.addEventListener('show.bs.collapse', async () => {
// Загружаем данные только если они еще не загружены
if (!toolkitStocksData && !isStocksLoading) {
await loadToolkitStocks();
}
});
// Обработчик для принудительной перезагрузки данных (например, при повторном открытии аккордеона)
const stocksHeading = modal.querySelector('#stocksHeading');
stocksHeading.addEventListener('click', async (e) => {
// Если данные уже загружены, можно обновить их при повторном клике
const isExpanded = stocksCollapse.classList.contains('show');
if (isExpanded && toolkitStocksData) {
// Можно добавить кнопку обновления или обновлять автоматически
// Для простоты пока оставляем как есть
// Обработчик события раскрытия аккордеона совместимости
const compatibilityCollapse = modal.querySelector('#compatibilityCollapse');
compatibilityCollapse.addEventListener('show.bs.collapse', async () => {
if (!compatibilityData && !isCompatibilityLoading) {
await loadCompatibilityData();
}
});
// Обработчик для кнопки добавления совместимости
const addCompatibilityBtn = modal.querySelector('#addCompatibilityBtn');
if (addCompatibilityBtn) {
addCompatibilityBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
showAddCompatibilityModal();
});
// Остановка всплытия события при клике на кнопку внутри аккордеона
const compatibilityHeading = modal.querySelector('#compatibilityHeading');
compatibilityHeading.addEventListener('click', (e) => {
if (addCompatibilityBtn.contains(e.target)) {
e.stopPropagation();
}
});
}
// Очистка при закрытии модалки
modal.addEventListener('hidden.bs.modal', () => {
modal.remove();