Files
sklad_legacy/js/catalog.js
cyrteL 13c64c503e Add files via upload
v0.2(css + js)
2025-07-28 14:21:39 +03:00

638 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// JavaScript для страницы каталога товаров
class CatalogManager {
constructor() {
this.warehouse = new WarehouseManager();
this.currentItems = [];
this.filteredItems = [];
this.currentPage = 1;
this.itemsPerPage = 12;
this.currentView = 'grid';
this.filters = {};
this.init();
}
init() {
$(document).ready(() => {
this.loadCatalog();
this.bindEvents();
this.initializeFilters();
});
}
bindEvents() {
// Поиск и фильтры
$('#searchInput').on('input', (e) => this.handleSearch(e.target.value));
$('#categoryFilter').on('change', (e) => this.handleCategoryFilter(e.target.value));
$('#priceMin, #priceMax').on('input', () => this.handlePriceFilter());
$('#inStockFilter, #lowStockFilter').on('change', () => this.applyFilters());
$('#clearFiltersBtn').on('click', () => this.clearFilters());
// Переключение вида
$('input[name="viewMode"]').on('change', (e) => {
this.currentView = e.target.id === 'gridView' ? 'grid' : 'list';
this.renderCatalog();
});
// Модальные окна
$('#saveItemBtn').on('click', () => this.addNewItem());
$('#updateItemBtn').on('click', () => this.updateItem());
// Экспорт
$('#exportBtn').on('click', () => this.exportCatalog());
}
// Загрузка каталога
async loadCatalog() {
try {
this.showLoading();
// Загружаем товары
this.currentItems = await this.warehouse.getAllItems();
// Загружаем категории
const categories = await this.warehouse.getCategories();
this.populateCategoryFilters(categories);
// Применяем фильтры и рендерим
this.applyFilters();
this.updateStatistics();
this.hideLoading();
} catch (error) {
console.error('Ошибка при загрузке каталога:', error);
auth.showNotification('Ошибка при загрузке каталога', 'danger');
this.hideLoading();
}
}
// Инициализация фильтров
initializeFilters() {
// Устанавливаем значения по умолчанию
this.filters = {
search: '',
category: '',
minPrice: null,
maxPrice: null,
inStock: false,
lowStock: false
};
}
// Заполнение фильтра категорий
populateCategoryFilters(categories) {
const categoryFilter = $('#categoryFilter');
const addItemCategory = $('#itemCategory');
const editItemCategory = $('#editItemCategory');
// Очищаем существующие опции (кроме первой)
categoryFilter.find('option:not(:first)').remove();
addItemCategory.find('option:not(:first)').remove();
editItemCategory.find('option:not(:first)').remove();
// Добавляем категории
categories.forEach(category => {
const option = `<option value="${category.id}">${category.name}</option>`;
categoryFilter.append(option);
addItemCategory.append(option);
editItemCategory.append(option);
});
}
// Обработка поиска
handleSearch(query) {
this.filters.search = query;
this.applyFilters();
}
// Обработка фильтра категории
handleCategoryFilter(category) {
this.filters.category = category;
this.applyFilters();
}
// Обработка фильтра цены
handlePriceFilter() {
this.filters.minPrice = $('#priceMin').val() ? parseFloat($('#priceMin').val()) : null;
this.filters.maxPrice = $('#priceMax').val() ? parseFloat($('#priceMax').val()) : null;
this.applyFilters();
}
// Применение всех фильтров
applyFilters() {
this.filters.inStock = $('#inStockFilter').is(':checked');
this.filters.lowStock = $('#lowStockFilter').is(':checked');
// Фильтруем товары
this.filteredItems = this.currentItems.filter(item => {
// Поиск по названию, описанию и штрих-коду
if (this.filters.search) {
const searchQuery = this.filters.search.toLowerCase();
const matchesSearch =
item.name.toLowerCase().includes(searchQuery) ||
item.description.toLowerCase().includes(searchQuery) ||
item.barcode.includes(searchQuery);
if (!matchesSearch) return false;
}
// Фильтр по категории
if (this.filters.category && item.category !== this.filters.category) {
return false;
}
// Фильтр по цене
if (this.filters.minPrice && item.price < this.filters.minPrice) {
return false;
}
if (this.filters.maxPrice && item.price > this.filters.maxPrice) {
return false;
}
// Фильтр по наличию
if (this.filters.inStock && item.quantity <= 0) {
return false;
}
// Фильтр по низкому запасу
if (this.filters.lowStock && item.quantity > item.minQuantity) {
return false;
}
return true;
});
this.currentPage = 1;
this.renderCatalog();
this.updateStatistics();
}
// Очистка фильтров
clearFilters() {
$('#searchInput').val('');
$('#categoryFilter').val('');
$('#priceMin').val('');
$('#priceMax').val('');
$('#inStockFilter').prop('checked', false);
$('#lowStockFilter').prop('checked', false);
this.initializeFilters();
this.applyFilters();
}
// Рендеринг каталога
renderCatalog() {
const container = $('#catalogContainer');
container.empty();
if (this.filteredItems.length === 0) {
container.html(`
<div class="col-12">
<div class="empty-state">
<i class="fas fa-search"></i>
<h4>Товары не найдены</h4>
<p>Попробуйте изменить параметры поиска или фильтры</p>
</div>
</div>
`);
return;
}
// Пагинация
const totalPages = Math.ceil(this.filteredItems.length / this.itemsPerPage);
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
const endIndex = startIndex + this.itemsPerPage;
const pageItems = this.filteredItems.slice(startIndex, endIndex);
// Рендерим товары
pageItems.forEach(item => {
const itemHtml = this.currentView === 'grid' ?
this.createGridItem(item) : this.createListItem(item);
container.append(itemHtml);
});
// Рендерим пагинацию
this.renderPagination(totalPages);
}
// Создание карточки товара (сетка)
createGridItem(item) {
const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item);
return `
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
<div class="card product-card h-100">
<div class="product-image">
<i class="${category.icon} fa-3x"></i>
</div>
<div class="card-body">
<h6 class="card-title">${item.name}</h6>
<p class="card-text text-muted small">${item.description.substring(0, 60)}${item.description.length > 60 ? '...' : ''}</p>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="badge bg-secondary">${category.name}</span>
<span class="badge ${stockStatus.class}">${stockStatus.text}</span>
</div>
<div class="row text-center mb-3">
<div class="col-6">
<small class="text-muted">Цена</small>
<div class="fw-bold">${item.price.toLocaleString()} ₽</div>
</div>
<div class="col-6">
<small class="text-muted">Количество</small>
<div class="fw-bold">${item.quantity} шт.</div>
</div>
</div>
<div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-sm" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-1"></i>Просмотр
</button>
${auth.hasPermission('write') ? `
<button class="btn btn-outline-warning btn-sm" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-1"></i>Редактировать
</button>
` : ''}
</div>
</div>
</div>
</div>
`;
}
// Создание строки товара (список)
createListItem(item) {
const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item);
return `
<div class="col-12 mb-3">
<div class="card">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-2">
<div class="text-center">
<i class="${category.icon} fa-2x text-muted"></i>
</div>
</div>
<div class="col-md-3">
<h6 class="mb-1">${item.name}</h6>
<small class="text-muted">${item.description.substring(0, 50)}${item.description.length > 50 ? '...' : ''}</small>
</div>
<div class="col-md-2">
<span class="badge bg-secondary">${category.name}</span>
</div>
<div class="col-md-2">
<div class="text-center">
<div class="fw-bold">${item.price.toLocaleString()} ₽</div>
<small class="text-muted">${item.quantity} шт.</small>
</div>
</div>
<div class="col-md-2">
<span class="badge ${stockStatus.class}">${stockStatus.text}</span>
</div>
<div class="col-md-1">
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-2"></i>Просмотр
</a></li>
${auth.hasPermission('write') ? `
<li><a class="dropdown-item" href="#" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-2"></i>Редактировать
</a></li>
` : ''}
${auth.hasPermission('delete') ? `
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="catalogManager.deleteItem(${item.id})">
<i class="fas fa-trash me-2"></i>Удалить
</a></li>
` : ''}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
`;
}
// Рендеринг пагинации
renderPagination(totalPages) {
const pagination = $('#pagination');
pagination.empty();
if (totalPages <= 1) return;
// Кнопка "Предыдущая"
const prevDisabled = this.currentPage === 1 ? 'disabled' : '';
pagination.append(`
<li class="page-item ${prevDisabled}">
<a class="page-link" href="#" onclick="catalogManager.goToPage(${this.currentPage - 1})">
<i class="fas fa-chevron-left"></i>
</a>
</li>
`);
// Номера страниц
const startPage = Math.max(1, this.currentPage - 2);
const endPage = Math.min(totalPages, this.currentPage + 2);
for (let i = startPage; i <= endPage; i++) {
const active = i === this.currentPage ? 'active' : '';
pagination.append(`
<li class="page-item ${active}">
<a class="page-link" href="#" onclick="catalogManager.goToPage(${i})">${i}</a>
</li>
`);
}
// Кнопка "Следующая"
const nextDisabled = this.currentPage === totalPages ? 'disabled' : '';
pagination.append(`
<li class="page-item ${nextDisabled}">
<a class="page-link" href="#" onclick="catalogManager.goToPage(${this.currentPage + 1})">
<i class="fas fa-chevron-right"></i>
</a>
</li>
`);
}
// Переход на страницу
goToPage(page) {
this.currentPage = page;
this.renderCatalog();
}
// Обновление статистики
updateStatistics() {
const totalItems = this.filteredItems.length;
const inStockItems = this.filteredItems.filter(item => item.quantity > 0).length;
const lowStockItems = this.filteredItems.filter(item => item.quantity <= item.minQuantity).length;
const categories = [...new Set(this.filteredItems.map(item => item.category))].length;
$('#totalItemsCount').text(totalItems);
$('#inStockCount').text(inStockItems);
$('#lowStockCount').text(lowStockItems);
$('#categoriesCount').text(categories);
}
// Получение информации о категории
getCategoryInfo(categoryId) {
const categories = [
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' },
{ id: 'clothing', name: 'Одежда', icon: 'fas fa-tshirt' },
{ id: 'tools', name: 'Инструменты', icon: 'fas fa-tools' },
{ id: 'office', name: 'Офисные принадлежности', icon: 'fas fa-briefcase' },
{ id: 'furniture', name: 'Мебель', icon: 'fas fa-couch' },
{ id: 'books', name: 'Книги', icon: 'fas fa-book' }
];
return categories.find(cat => cat.id === categoryId) ||
{ id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
}
// Получение статуса запаса
getStockStatus(item) {
if (item.quantity === 0) {
return { class: 'bg-danger', text: 'Нет в наличии' };
} else if (item.quantity <= item.minQuantity) {
return { class: 'bg-warning', text: 'Заканчивается' };
} else {
return { class: 'bg-success', text: 'В наличии' };
}
}
// Просмотр товара
viewItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId);
if (!item) return;
// Здесь можно открыть модальное окно с подробной информацией
auth.showNotification(`Просмотр товара: ${item.name}`, 'info');
}
// Редактирование товара
async editItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId);
if (!item) return;
// Заполняем форму редактирования
$('#editItemId').val(item.id);
$('#editItemName').val(item.name);
$('#editItemCategory').val(item.category);
$('#editItemPrice').val(item.price);
$('#editItemQuantity').val(item.quantity);
$('#editItemDescription').val(item.description);
$('#editItemBarcode').val(item.barcode);
$('#editItemLocation').val(item.location);
$('#editItemSupplier').val(item.supplier);
$('#editItemMinQuantity').val(item.minQuantity);
// Открываем модальное окно
$('#editItemModal').modal('show');
}
// Обновление товара
async updateItem() {
const formData = this.getFormData('#editItemForm');
if (!this.validateForm(formData)) {
return;
}
try {
const itemId = parseInt(formData.editItemId);
const updateData = {
name: formData.editItemName,
category: formData.editItemCategory,
price: parseFloat(formData.editItemPrice),
quantity: parseInt(formData.editItemQuantity),
description: formData.editItemDescription || '',
barcode: formData.editItemBarcode || '',
location: formData.editItemLocation || '',
supplier: formData.editItemSupplier || '',
minQuantity: parseInt(formData.editItemMinQuantity) || 0
};
await this.warehouse.updateItem(itemId, updateData);
// Обновляем локальные данные
const itemIndex = this.currentItems.findIndex(i => i.id === itemId);
if (itemIndex !== -1) {
this.currentItems[itemIndex] = { ...this.currentItems[itemIndex], ...updateData };
}
auth.showNotification('Товар успешно обновлен', 'success');
// Закрываем модальное окно и обновляем каталог
$('#editItemModal').modal('hide');
this.applyFilters();
} catch (error) {
console.error('Ошибка при обновлении товара:', error);
auth.showNotification('Ошибка при обновлении товара', 'danger');
}
}
// Добавление нового товара
async addNewItem() {
const formData = this.getFormData('#addItemForm');
if (!this.validateForm(formData)) {
return;
}
try {
const newItem = await this.warehouse.addItem(formData);
// Добавляем в локальные данные
this.currentItems.push(newItem);
auth.showNotification('Товар успешно добавлен', 'success');
// Закрываем модальное окно и обновляем каталог
$('#addItemModal').modal('hide');
this.applyFilters();
// Очищаем форму
$('#addItemForm')[0].reset();
} catch (error) {
console.error('Ошибка при добавлении товара:', error);
auth.showNotification('Ошибка при добавлении товара', 'danger');
}
}
// Удаление товара
async deleteItem(itemId) {
if (!confirm('Вы уверены, что хотите удалить этот товар?')) {
return;
}
try {
await this.warehouse.deleteItem(itemId);
// Удаляем из локальных данных
this.currentItems = this.currentItems.filter(i => i.id !== itemId);
auth.showNotification('Товар успешно удален', 'success');
this.applyFilters();
} catch (error) {
console.error('Ошибка при удалении товара:', error);
auth.showNotification('Ошибка при удалении товара', 'danger');
}
}
// Получение данных формы
getFormData(formSelector) {
const form = $(formSelector);
const formData = {};
form.find('input, select, textarea').each(function() {
const field = $(this);
const name = field.attr('id');
const value = field.val();
if (name) {
formData[name] = value;
}
});
return formData;
}
// Валидация формы
validateForm(formData) {
const requiredFields = ['itemName', 'itemCategory', 'itemPrice', 'itemQuantity'];
for (const field of requiredFields) {
if (!formData[field] || formData[field].trim() === '') {
auth.showNotification(`Поле "${this.getFieldLabel(field)}" обязательно для заполнения`, 'warning');
return false;
}
}
// Проверка числовых значений
if (formData.itemPrice && parseFloat(formData.itemPrice) < 0) {
auth.showNotification('Цена не может быть отрицательной', 'warning');
return false;
}
if (formData.itemQuantity && parseInt(formData.itemQuantity) < 0) {
auth.showNotification('Количество не может быть отрицательным', 'warning');
return false;
}
return true;
}
// Получение метки поля
getFieldLabel(fieldName) {
const labels = {
'itemName': 'Название товара',
'itemCategory': 'Категория',
'itemPrice': 'Цена',
'itemQuantity': 'Количество',
'editItemName': 'Название товара',
'editItemCategory': 'Категория',
'editItemPrice': 'Цена',
'editItemQuantity': 'Количество'
};
return labels[fieldName] || fieldName;
}
// Экспорт каталога
async exportCatalog() {
try {
const exportData = await this.warehouse.exportData('items');
// Создаем CSV
let csv = exportData.headers.join(',') + '\n';
exportData.rows.forEach(row => {
csv += row.map(cell => `"${cell}"`).join(',') + '\n';
});
// Скачиваем файл
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `каталоговаров_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
auth.showNotification('Каталог успешно экспортирован', 'success');
} catch (error) {
console.error('Ошибка при экспорте:', error);
auth.showNotification('Ошибка при экспорте каталога', 'danger');
}
}
// Показать индикатор загрузки
showLoading() {
$('#catalogContainer').html(`
<div class="col-12 text-center py-5">
<div class="loading-spinner text-primary mb-3"></div>
<p class="text-muted">Загрузка каталога...</p>
</div>
`);
}
// Скрыть индикатор загрузки
hideLoading() {
// Загрузка завершается в renderCatalog()
}
}
// Инициализация менеджера каталога
const catalogManager = new CatalogManager();