Add files via upload

v0.2(css + js)
This commit is contained in:
cyrteL
2025-07-28 14:21:39 +03:00
committed by GitHub
parent 0308719223
commit 13c64c503e
8 changed files with 4103 additions and 0 deletions

638
js/catalog.js Normal file
View File

@ -0,0 +1,638 @@
// 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();