Add files via upload
v0.0.3
This commit is contained in:
182
README.md
182
README.md
@ -1,18 +1,27 @@
|
||||
# Система управления складом "Склад-Менеджер"
|
||||
|
||||
Современная веб-система для управления товарными запасами и операциями склада с разными уровнями доступа.
|
||||
Современная веб-система для управления складом деталей и операциями с разными уровнями доступа.
|
||||
|
||||
## 🚀 Возможности системы
|
||||
|
||||
### Основной функционал
|
||||
- **Управление товарами**: добавление, редактирование, удаление товаров
|
||||
- **Операции склада**: приход и расход товаров
|
||||
- **Каталог товаров**: удобный просмотр с фильтрами и поиском
|
||||
- **Управление позициями**: добавление, редактирование, удаление позиций
|
||||
- **Операции склада**: приход и расход позиций
|
||||
- **Каталог позиций**: удобный просмотр с фильтрами и поиском
|
||||
- **История операций**: полный учет всех операций со складом
|
||||
- **Поиск**: быстрый поиск товаров и операций
|
||||
- **Поиск**: быстрый поиск позиций и операций
|
||||
- **Отчеты**: генерация различных отчетов
|
||||
- **Экспорт данных**: выгрузка в CSV и JSON форматах
|
||||
|
||||
### Система безопасности
|
||||
- **Многоуровневая авторизация** с разными ролями
|
||||
- **Контроль доступа** к функциям системы
|
||||
- **Безопасные сессии** пользователей
|
||||
- **Валидация данных** на всех уровнях
|
||||
- **Проверка прав доступа** для каждой операции
|
||||
- **Безопасная обработка ошибок** с информативными уведомлениями
|
||||
- **URL валидация** параметров и фильтров
|
||||
|
||||
### Уровни доступа
|
||||
|
||||
#### 🔧 Администратор (admin/admin123)
|
||||
@ -53,10 +62,11 @@ warehouse-system/
|
||||
│ ├── auth.js # Система авторизации
|
||||
│ ├── main.js # Основная логика
|
||||
│ ├── warehouse.js # Управление складом
|
||||
│ ├── catalog.js # Каталог товаров
|
||||
│ ├── catalog.js # Каталог товаров (с проверкой прав доступа)
|
||||
│ ├── history.js # История операций
|
||||
│ ├── search.js # Поиск
|
||||
│ └── profile.js # Профиль пользователя
|
||||
│ ├── profile.js # Профиль пользователя
|
||||
│ └── categories.js # Управление категориями
|
||||
└── README.md # Документация
|
||||
```
|
||||
|
||||
@ -72,7 +82,22 @@ warehouse-system/
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Первый вход
|
||||
### 1. Установка
|
||||
1. Скачайте или клонируйте проект
|
||||
2. Откройте `index.html` в браузере
|
||||
3. Или запустите локальный сервер:
|
||||
```bash
|
||||
# Python 3
|
||||
python -m http.server 8000
|
||||
|
||||
# Node.js
|
||||
npx http-server
|
||||
|
||||
# PHP
|
||||
php -S localhost:8000
|
||||
```
|
||||
|
||||
### 2. Первый вход
|
||||
1. Откройте `login.html`
|
||||
2. Используйте тестовые учетные записи:
|
||||
- **Администратор**: `admin` / `admin123`
|
||||
@ -80,7 +105,7 @@ warehouse-system/
|
||||
- **Оператор**: `operator` / `operator123`
|
||||
- **Наблюдатель**: `viewer` / `viewer123`
|
||||
|
||||
### 2. Начальная настройка
|
||||
### 3. Начальная настройка
|
||||
1. Войдите как администратор
|
||||
2. Добавьте первые товары через главную страницу
|
||||
3. Настройте категории товаров
|
||||
@ -92,12 +117,24 @@ warehouse-system/
|
||||
- **Быстрые действия**: добавление товаров, приход/расход
|
||||
- **Статистика**: общая информация о складе
|
||||
- **Последние операции**: мониторинг активности
|
||||
- **Быстрая навигация**: выпадающее меню категорий
|
||||
|
||||
### Каталог товаров
|
||||
- **Просмотр**: все товары с фильтрами
|
||||
### Навигация по категориям
|
||||
- **Выпадающее меню**: появляется при наведении на "Каталог"
|
||||
- **Быстрый переход**: клик по категории открывает отфильтрованный каталог
|
||||
- **URL фильтрация**: прямые ссылки вида `catalog.html?category=electronics`
|
||||
- **Визуальная обратная связь**: иконки и цвета категорий
|
||||
- **Единообразие**: одинаковое меню на всех страницах
|
||||
|
||||
### Каталог позиций
|
||||
- **Просмотр**: все позиции с фильтрами и статистикой
|
||||
- **Поиск**: по названию, описанию, штрих-коду
|
||||
- **Фильтры**: по категории, цене, наличию
|
||||
- **Редактирование**: изменение информации о товарах
|
||||
- **Фильтры**: по категории, цене, наличию, статусу запасов
|
||||
- **Редактирование**: изменение информации о позициях (только для админов и менеджеров)
|
||||
- **Экспорт**: выгрузка каталога в CSV формате
|
||||
- **Безопасность**: проверка прав доступа для всех операций
|
||||
- **Быстрая навигация**: выпадающее меню категорий при наведении
|
||||
- **URL фильтрация**: прямая ссылка на отфильтрованный каталог
|
||||
|
||||
### История операций
|
||||
- **Просмотр**: все операции с детализацией
|
||||
@ -106,10 +143,11 @@ warehouse-system/
|
||||
- **Экспорт**: выгрузка данных
|
||||
|
||||
### Поиск
|
||||
- **Универсальный поиск**: товары и операции
|
||||
- **Универсальный поиск**: позиции и операции
|
||||
- **Расширенные фильтры**: точная настройка
|
||||
- **Результаты**: с подсветкой найденного
|
||||
- **Экспорт результатов**: сохранение найденного
|
||||
- **Быстрая навигация**: переход к категориям из результатов поиска
|
||||
|
||||
### Профиль пользователя
|
||||
- **Информация**: личные данные и права
|
||||
@ -126,9 +164,11 @@ warehouse-system/
|
||||
4. Создавайте, редактируйте, удаляйте пользователей
|
||||
|
||||
### Настройка категорий
|
||||
1. Откройте "Категории товаров"
|
||||
1. Откройте "Категории позиций"
|
||||
2. Добавьте нужные категории
|
||||
3. Настройте иконки и описания
|
||||
4. Категории автоматически появятся в выпадающем меню
|
||||
5. Изменения применяются мгновенно на всех страницах
|
||||
|
||||
### Генерация отчетов
|
||||
1. Выберите тип отчета
|
||||
@ -138,7 +178,7 @@ warehouse-system/
|
||||
|
||||
## 📊 Структура данных
|
||||
|
||||
### Товар
|
||||
### Позиция
|
||||
```javascript
|
||||
{
|
||||
id: 1,
|
||||
@ -151,8 +191,8 @@ warehouse-system/
|
||||
minQuantity: 10,
|
||||
location: "Стеллаж A-1",
|
||||
supplier: "Поставщик",
|
||||
createdAt: "2024-01-15T10:00:00Z",
|
||||
updatedAt: "2024-01-15T10:00:00Z"
|
||||
createdAt: "2025-01-15T10:00:00Z",
|
||||
updatedAt: "2025-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@ -166,7 +206,7 @@ warehouse-system/
|
||||
quantity: 10,
|
||||
employeeId: 1,
|
||||
employeeName: "Имя сотрудника",
|
||||
date: "2024-01-15T10:00:00Z",
|
||||
date: "2025-01-15T10:00:00Z",
|
||||
status: "completed",
|
||||
supplier: "Поставщик",
|
||||
recipient: "Получатель",
|
||||
@ -188,6 +228,21 @@ warehouse-system/
|
||||
}
|
||||
```
|
||||
|
||||
### Категория
|
||||
```javascript
|
||||
{
|
||||
id: "electronics",
|
||||
name: "Электроника",
|
||||
description: "Электронные компоненты и устройства",
|
||||
icon: "fas fa-microchip",
|
||||
color: "#2c5aa0",
|
||||
active: true,
|
||||
itemCount: 25,
|
||||
createdAt: "2025-01-15T10:00:00Z",
|
||||
updatedAt: "2025-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 Кастомизация
|
||||
|
||||
### Изменение цветовой схемы
|
||||
@ -209,7 +264,20 @@ warehouse-system/
|
||||
{
|
||||
id: 'new-category',
|
||||
name: 'Новая категория',
|
||||
icon: 'fas fa-icon-name'
|
||||
icon: 'fas fa-icon-name',
|
||||
color: '#2c5aa0',
|
||||
description: 'Описание категории',
|
||||
active: true
|
||||
}
|
||||
```
|
||||
|
||||
### Настройка выпадающего меню
|
||||
Категории автоматически появляются в выпадающем меню навигации. Для настройки стилей отредактируйте CSS в `css/style.css`:
|
||||
```css
|
||||
#catalogCategoriesDropdown {
|
||||
min-width: 250px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
```
|
||||
|
||||
@ -232,6 +300,50 @@ warehouse-system/
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
## 📈 Производительность
|
||||
|
||||
### Оптимизация
|
||||
- Используйте современный браузер
|
||||
- Включите кэширование
|
||||
- Минимизируйте количество одновременных операций
|
||||
- Регулярно очищайте старые данные
|
||||
|
||||
### Масштабирование
|
||||
- Для больших объемов данных используйте базу данных
|
||||
- Добавьте серверную часть (PHP, Node.js, Python)
|
||||
- Используйте CDN для статических файлов
|
||||
- Настройте кэширование на сервере
|
||||
- Оптимизируйте загрузку категорий для больших каталогов
|
||||
|
||||
### Технические улучшения
|
||||
- **Безопасная обработка данных**: проверка на null/undefined
|
||||
- **Валидация форм**: клиентская проверка ввода
|
||||
- **Обработка ошибок**: информативные уведомления
|
||||
- **Права доступа**: детальная проверка ролей
|
||||
- **Оптимизация производительности**: эффективная фильтрация
|
||||
- **URL маршрутизация**: обработка параметров и фильтров
|
||||
- **Асинхронная загрузка**: динамическое обновление меню
|
||||
- **Кэширование данных**: оптимизация производительности
|
||||
|
||||
## 🔄 Последние обновления
|
||||
|
||||
### Версия 1.2.0 (Январь 2025)
|
||||
- ✅ **Добавлено выпадающее меню категорий**: быстрая навигация по категориям
|
||||
- ✅ **Реализована URL фильтрация**: прямые ссылки на отфильтрованный каталог
|
||||
- ✅ **Улучшена навигация**: единообразное меню на всех страницах
|
||||
- ✅ **Добавлены анимации**: плавные эффекты появления меню
|
||||
- ✅ **Улучшен UX**: hover эффекты и визуальная обратная связь
|
||||
- ✅ **Расширена функциональность**: переходы между категориями из любого места
|
||||
|
||||
### Версия 1.1.0 (Январь 2025)
|
||||
- ✅ **Улучшена безопасность каталога**: добавлена проверка прав доступа
|
||||
- ✅ **Исправлена навигация**: добавлена ссылка "Поиск" в главное меню
|
||||
- ✅ **Улучшена обработка ошибок**: безопасная работа с пустыми данными
|
||||
- ✅ **Добавлена валидация**: проверка на null/undefined значения
|
||||
- ✅ **Улучшена статистика**: безопасный подсчет с fallback значениями
|
||||
- ✅ **Исправлены отступы**: консистентная структура кода
|
||||
- ✅ **Добавлены уведомления**: информативные сообщения об ошибках
|
||||
|
||||
## 🐛 Устранение неполадок
|
||||
|
||||
### Частые проблемы
|
||||
@ -240,6 +352,7 @@ sudo certbot --nginx -d your-domain.com
|
||||
- Проверьте правильность логина/пароля
|
||||
- Очистите кэш браузера
|
||||
- Проверьте консоль на ошибки JavaScript
|
||||
- Убедитесь, что JavaScript включен в браузере
|
||||
|
||||
#### Не сохраняются данные
|
||||
- Проверьте доступ к LocalStorage
|
||||
@ -251,6 +364,14 @@ sudo certbot --nginx -d your-domain.com
|
||||
- Очистите кэш браузера
|
||||
- Проверьте количество товаров/операций
|
||||
|
||||
#### Проблемы с каталогом
|
||||
- Проверьте права доступа пользователя
|
||||
- Убедитесь, что категории загружены корректно
|
||||
- Проверьте консоль на ошибки загрузки данных
|
||||
- Очистите фильтры, если каталог пустой
|
||||
- Проверьте URL параметры для фильтрации по категориям
|
||||
- Убедитесь, что выпадающее меню категорий загружается
|
||||
|
||||
### Логи и отладка
|
||||
Откройте консоль разработчика (F12) для просмотра ошибок и логов.
|
||||
|
||||
@ -271,17 +392,22 @@ sudo certbot --nginx -d your-domain.com
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Этот проект распространяется под лицензией MIT.
|
||||
Этот проект распространяется под лицензией MIT. См. файл LICENSE для подробностей.
|
||||
|
||||
## 👥 Авторы
|
||||
|
||||
- Разработка: Roman Pylaev
|
||||
- Дизайн: Roman Pylaev
|
||||
- Тестирование: Roman Pylaev
|
||||
- Разработка: [Ваше имя]
|
||||
- Дизайн: [Имя дизайнера]
|
||||
- Тестирование: [Имя тестировщика]
|
||||
|
||||
## 📞 Контакты
|
||||
|
||||
- Email: r0manpylaev@yandex.ru
|
||||
- Телефон: +7 (977) 665-45-78
|
||||
- Telegram: @dark7es
|
||||
- Discord: @dark7es
|
||||
- Email: support@warehouse-system.com
|
||||
- Телефон: +7 (XXX) XXX-XX-XX
|
||||
- Веб-сайт: https://warehouse-system.com
|
||||
|
||||
---
|
||||
|
||||
**Версия**: 1.2.0
|
||||
**Дата**: Январь 2025
|
||||
**Последнее обновление**: 15.01.2025
|
||||
338
admin/categories.html
Normal file
338
admin/categories.html
Normal file
@ -0,0 +1,338 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Управление категориями - Склад-Менеджер</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="../css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="../index.html">
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="../catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../history.html">
|
||||
<i class="fas fa-history me-1"></i>История
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item active" href="categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="../profile.html">Мой профиль</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" id="logoutBtn">Выйти</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="../index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="mb-1">
|
||||
<i class="fas fa-tags me-2"></i>
|
||||
Управление категориями
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Создание и редактирование категорий позиций</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
|
||||
<i class="fas fa-plus me-1"></i>Добавить категорию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Статистика категорий -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="totalCategories">0</h4>
|
||||
<p class="card-text">Всего категорий</p>
|
||||
</div>
|
||||
<i class="fas fa-tags fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="totalItems">0</h4>
|
||||
<p class="card-text">Всего позиций</p>
|
||||
</div>
|
||||
<i class="fas fa-boxes fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="activeCategories">0</h4>
|
||||
<p class="card-text">Активные категории</p>
|
||||
</div>
|
||||
<i class="fas fa-check-circle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card bg-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="emptyCategories">0</h4>
|
||||
<p class="card-text">Пустые категории</p>
|
||||
</div>
|
||||
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Таблица категорий -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-list me-2"></i>
|
||||
Список категорий
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="categoriesTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Описание</th>
|
||||
<th>Иконка</th>
|
||||
<th>Количество позиций</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Модальное окно добавления категории -->
|
||||
<div class="modal fade" id="addCategoryModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-plus me-2"></i>
|
||||
Добавить категорию
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="addCategoryForm">
|
||||
<div class="mb-3">
|
||||
<label for="categoryName" class="form-label">Название категории *</label>
|
||||
<input type="text" class="form-control" id="categoryName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="categoryDescription" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="categoryDescription" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="categoryIcon" class="form-label">Иконка</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-icons"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="categoryIcon" placeholder="fas fa-box">
|
||||
</div>
|
||||
<small class="form-text text-muted">Используйте классы Font Awesome (например: fas fa-box)</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="categoryColor" class="form-label">Цвет категории</label>
|
||||
<input type="color" class="form-control form-control-color" id="categoryColor" value="#2c5aa0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="categoryActive" checked>
|
||||
<label class="form-check-label" for="categoryActive">
|
||||
Активная категория
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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="saveCategoryBtn">
|
||||
<i class="fas fa-save me-1"></i>Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно редактирования категории -->
|
||||
<div class="modal fade" id="editCategoryModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-edit me-2"></i>
|
||||
Редактировать категорию
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editCategoryForm">
|
||||
<input type="hidden" id="editCategoryId">
|
||||
<div class="mb-3">
|
||||
<label for="editCategoryName" class="form-label">Название категории *</label>
|
||||
<input type="text" class="form-control" id="editCategoryName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCategoryDescription" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="editCategoryDescription" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCategoryIcon" class="form-label">Иконка</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-icons"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="editCategoryIcon" placeholder="fas fa-box">
|
||||
</div>
|
||||
<small class="form-text text-muted">Используйте классы Font Awesome (например: fas fa-box)</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCategoryColor" class="form-label">Цвет категории</label>
|
||||
<input type="color" class="form-control form-control-color" id="editCategoryColor" value="#2c5aa0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="editCategoryActive">
|
||||
<label class="form-check-label" for="editCategoryActive">
|
||||
Активная категория
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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="updateCategoryBtn">
|
||||
<i class="fas fa-save me-1"></i>Обновить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Подвал -->
|
||||
<footer class="bg-dark text-white mt-5 py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>Система управления складом</h5>
|
||||
<p class="mb-0">Эффективное управление складом деталей</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">© 2025 Склад-Менеджер. Все права защищены.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Скрипты -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="../js/auth.js"></script>
|
||||
<script src="../js/warehouse.js"></script>
|
||||
<script src="../js/categories.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
72
catalog.html
72
catalog.html
@ -9,27 +9,46 @@
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-warehouse me-2"></i>
|
||||
Склад-Менеджер
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="catalog.html">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="history.html">
|
||||
@ -41,19 +60,17 @@
|
||||
<i class="fas fa-search me-1"></i>Поиск
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown">
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
@ -68,6 +85,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
<!-- Заголовок страницы -->
|
||||
@ -77,16 +112,17 @@
|
||||
<div>
|
||||
<h1 class="mb-1">
|
||||
<i class="fas fa-boxes me-2"></i>
|
||||
Каталог товаров
|
||||
Каталог позиций
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Управление товарными позициями склада</p>
|
||||
<p class="text-muted mb-0">Управление позициями склада</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-outline-primary" id="exportBtn">
|
||||
<i class="fas fa-download me-1"></i>Экспорт
|
||||
</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn">
|
||||
<i class="fas fa-plus me-1"></i>Добавить товар
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn"
|
||||
style="display: none;" title="Требуются права администратора или менеджера">
|
||||
<i class="fas fa-plus me-1"></i>Добавить позицию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -97,7 +133,7 @@
|
||||
<div class="catalog-filters">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="searchInput" class="form-label">Поиск товаров</label>
|
||||
<label for="searchInput" class="form-label">Поиск позиций</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-search"></i>
|
||||
@ -154,7 +190,7 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="totalItemsCount">0</h4>
|
||||
<p class="card-text">Всего товаров</p>
|
||||
<p class="card-text">Всего позиций</p>
|
||||
</div>
|
||||
<i class="fas fa-boxes fa-2x"></i>
|
||||
</div>
|
||||
@ -400,4 +436,4 @@
|
||||
<script src="js/warehouse.js"></script>
|
||||
<script src="js/catalog.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
228
css/style.css
228
css/style.css
@ -1,17 +1,20 @@
|
||||
/* Основные стили для системы управления складом */
|
||||
|
||||
:root {
|
||||
--primary-color: #0d6efd;
|
||||
--primary-color: #2c5aa0;
|
||||
--secondary-color: #6c757d;
|
||||
--success-color: #198754;
|
||||
--success-color: #28a745;
|
||||
--warning-color: #ffc107;
|
||||
--danger-color: #dc3545;
|
||||
--info-color: #0dcaf0;
|
||||
--info-color: #17a2b8;
|
||||
--light-color: #f8f9fa;
|
||||
--dark-color: #212529;
|
||||
--border-radius: 0.375rem;
|
||||
--box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--transition: all 0.15s ease-in-out;
|
||||
--dark-color: #343a40;
|
||||
--border-radius: 0.25rem;
|
||||
--box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--transition: all 0.2s ease-in-out;
|
||||
--header-bg: #1e3a5f;
|
||||
--sidebar-bg: #f8f9fa;
|
||||
--card-border: #e9ecef;
|
||||
}
|
||||
|
||||
/* Общие стили */
|
||||
@ -21,40 +24,124 @@ body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Навигация */
|
||||
|
||||
|
||||
/* Главная навигация */
|
||||
.navbar {
|
||||
background: #fff !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.navbar-search .input-group {
|
||||
border-radius: 25px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.navbar-search .form-control {
|
||||
border: none;
|
||||
padding: 0.75rem 1.25rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.navbar-search .btn {
|
||||
border: none;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 0 25px 25px 0;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
padding: 0.5rem 1rem !important;
|
||||
border-radius: var(--border-radius);
|
||||
margin: 0 0.25rem;
|
||||
color: var(--dark-color) !important;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link:hover {
|
||||
color: rgba(255, 255, 255, 0.8) !important;
|
||||
background-color: rgba(44, 90, 160, 0.1);
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link.active {
|
||||
color: var(--primary-color) !important;
|
||||
background-color: rgba(44, 90, 160, 0.15);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Панель категорий */
|
||||
.categories-bar {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-bottom: 1px solid var(--card-border);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.categories-nav {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.categories-nav .nav-link {
|
||||
border-radius: 20px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
color: var(--dark-color);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.categories-nav .nav-link:hover {
|
||||
background-color: rgba(44, 90, 160, 0.1);
|
||||
border-color: rgba(44, 90, 160, 0.2);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.categories-nav .nav-link.active {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
|
||||
color: #fff;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 2px 8px rgba(44, 90, 160, 0.3);
|
||||
}
|
||||
|
||||
/* Карточки быстрых действий */
|
||||
.quick-action-card {
|
||||
transition: var(--transition);
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
border: 1px solid var(--card-border);
|
||||
box-shadow: var(--box-shadow);
|
||||
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.quick-action-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.quick-action-card .card-body {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
.quick-action-card i {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Градиентный фон для приветственного блока */
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%);
|
||||
background: linear-gradient(135deg, var(--header-bg) 0%, var(--primary-color) 100%);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Статистические карточки */
|
||||
@ -65,35 +152,56 @@ body {
|
||||
border: none;
|
||||
box-shadow: var(--box-shadow);
|
||||
transition: var(--transition);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card.bg-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%) !important;
|
||||
}
|
||||
|
||||
.card.bg-success {
|
||||
background: linear-gradient(135deg, var(--success-color) 0%, #1e7e34 100%) !important;
|
||||
}
|
||||
|
||||
.card.bg-warning {
|
||||
background: linear-gradient(135deg, var(--warning-color) 0%, #e0a800 100%) !important;
|
||||
}
|
||||
|
||||
.card.bg-info {
|
||||
background: linear-gradient(135deg, var(--info-color) 0%, #138496 100%) !important;
|
||||
}
|
||||
|
||||
.card.bg-primary:hover,
|
||||
.card.bg-success:hover,
|
||||
.card.bg-warning:hover,
|
||||
.card.bg-info:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Таблицы */
|
||||
.table {
|
||||
background-color: white;
|
||||
border-radius: var(--border-radius);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--box-shadow);
|
||||
border: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
background-color: var(--light-color);
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
background: linear-gradient(135deg, var(--light-color) 0%, #e9ecef 100%);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.875rem;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--dark-color);
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
background-color: rgba(44, 90, 160, 0.05);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
/* Модальные окна */
|
||||
@ -115,15 +223,23 @@ body {
|
||||
/* Формы */
|
||||
.form-control,
|
||||
.form-select {
|
||||
border-radius: var(--border-radius);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ced4da;
|
||||
transition: var(--transition);
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.form-control:focus,
|
||||
.form-select:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
box-shadow: 0 0 0 0.2rem rgba(44, 90, 160, 0.25);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
border-radius: 8px 0 0 8px;
|
||||
border: 1px solid #ced4da;
|
||||
background: linear-gradient(135deg, var(--light-color) 0%, #e9ecef 100%);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@ -134,15 +250,26 @@ body {
|
||||
|
||||
/* Кнопки */
|
||||
.btn {
|
||||
border-radius: var(--border-radius);
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
text-transform: none;
|
||||
border: none;
|
||||
padding: 0.5rem 1.25rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, var(--primary-color) 100%);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@ -440,4 +567,57 @@ footer {
|
||||
border: 1px solid #000 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Стили для выпадающего меню категорий */
|
||||
#catalogCategoriesDropdown {
|
||||
min-width: 250px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
margin: 0 0.5rem;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-divider {
|
||||
margin: 0.5rem 1rem;
|
||||
border-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Анимация появления выпадающего меню */
|
||||
.dropdown-menu.show {
|
||||
animation: dropdownFadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes dropdownFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
64
history.html
64
history.html
@ -9,51 +9,63 @@
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-warehouse me-2"></i>
|
||||
Склад-Менеджер
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="history.html">
|
||||
<i class="fas fa-history me-1"></i>История
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="search.html">
|
||||
<i class="fas fa-search me-1"></i>Поиск
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown">
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
@ -68,6 +80,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
<!-- Заголовок страницы -->
|
||||
@ -377,4 +407,4 @@
|
||||
<script src="js/warehouse.js"></script>
|
||||
<script src="js/history.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
134
index.html
134
index.html
@ -9,27 +9,41 @@
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-warehouse me-2"></i>
|
||||
Склад-Менеджер
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="history.html">
|
||||
@ -41,19 +55,17 @@
|
||||
<i class="fas fa-search me-1"></i>Поиск
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown">
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
@ -68,6 +80,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
<!-- Приветственный блок -->
|
||||
@ -79,7 +109,7 @@
|
||||
<i class="fas fa-warehouse me-3"></i>
|
||||
Добро пожаловать в систему управления складом
|
||||
</h1>
|
||||
<p class="card-text">Эффективное управление товарными запасами и операциями склада</p>
|
||||
<p class="card-text">Эффективное управление складом деталей и операциями</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,8 +124,8 @@
|
||||
<div class="card h-100 quick-action-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-plus-circle fa-3x text-primary mb-3"></i>
|
||||
<h5 class="card-title">Добавить товар</h5>
|
||||
<p class="card-text">Добавить новый товар в каталог</p>
|
||||
<h5 class="card-title">Добавить позицию</h5>
|
||||
<p class="card-text">Добавить новую позицию в каталог</p>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||
Добавить
|
||||
</button>
|
||||
@ -107,7 +137,7 @@
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-box-open fa-3x text-success mb-3"></i>
|
||||
<h5 class="card-title">Приход</h5>
|
||||
<p class="card-text">Оформить приход товара</p>
|
||||
<p class="card-text">Оформить приход позиции</p>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#incomingModal">
|
||||
Оформить
|
||||
</button>
|
||||
@ -119,7 +149,7 @@
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-truck fa-3x text-warning mb-3"></i>
|
||||
<h5 class="card-title">Расход</h5>
|
||||
<p class="card-text">Оформить расход товара</p>
|
||||
<p class="card-text">Оформить расход позиции</p>
|
||||
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#outgoingModal">
|
||||
Оформить
|
||||
</button>
|
||||
@ -131,13 +161,57 @@
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-search fa-3x text-info mb-3"></i>
|
||||
<h5 class="card-title">Поиск</h5>
|
||||
<p class="card-text">Найти товар или операцию</p>
|
||||
<p class="card-text">Найти позицию или операцию</p>
|
||||
<a href="search.html" class="btn btn-info">Найти</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дополнительные действия -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-user fa-3x text-secondary mb-3"></i>
|
||||
<h5 class="card-title">Мой профиль</h5>
|
||||
<p class="card-text">Управление профилем и настройками</p>
|
||||
<a href="profile.html" class="btn btn-secondary">Открыть</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-history fa-3x text-dark mb-3"></i>
|
||||
<h5 class="card-title">История операций</h5>
|
||||
<p class="card-text">Просмотр истории всех операций</p>
|
||||
<a href="history.html" class="btn btn-dark">Просмотреть</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-boxes fa-3x text-primary mb-3"></i>
|
||||
<h5 class="card-title">Каталог позиций</h5>
|
||||
<p class="card-text">Просмотр и управление позициями</p>
|
||||
<a href="catalog.html" class="btn btn-primary">Открыть</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-cog fa-3x text-warning mb-3"></i>
|
||||
<h5 class="card-title">Администрирование</h5>
|
||||
<p class="card-text">Управление системой и пользователями</p>
|
||||
<a href="admin/categories.html" class="btn btn-warning">Управлять</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Статистика -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
@ -149,7 +223,7 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<h4 class="card-title" id="totalItems">0</h4>
|
||||
<p class="card-text">Всего товаров</p>
|
||||
<p class="card-text">Всего позиций</p>
|
||||
</div>
|
||||
<i class="fas fa-boxes fa-2x"></i>
|
||||
</div>
|
||||
@ -247,7 +321,7 @@
|
||||
<form id="addItemForm">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="itemName" class="form-label">Название товара *</label>
|
||||
<label for="itemName" class="form-label">Название позиции *</label>
|
||||
<input type="text" class="form-control" id="itemName" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
@ -305,9 +379,9 @@
|
||||
<div class="modal-body">
|
||||
<form id="incomingForm">
|
||||
<div class="mb-3">
|
||||
<label for="incomingItem" class="form-label">Товар *</label>
|
||||
<label for="incomingItem" class="form-label">Позиция *</label>
|
||||
<select class="form-select" id="incomingItem" required>
|
||||
<option value="">Выберите товар</option>
|
||||
<option value="">Выберите позицию</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@ -348,9 +422,9 @@
|
||||
<div class="modal-body">
|
||||
<form id="outgoingForm">
|
||||
<div class="mb-3">
|
||||
<label for="outgoingItem" class="form-label">Товар *</label>
|
||||
<label for="outgoingItem" class="form-label">Позиция *</label>
|
||||
<select class="form-select" id="outgoingItem" required>
|
||||
<option value="">Выберите товар</option>
|
||||
<option value="">Выберите позицию</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@ -383,7 +457,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>Система управления складом</h5>
|
||||
<p class="mb-0">Эффективное управление товарными запасами</p>
|
||||
<p class="mb-0">Эффективное управление складом деталей</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">© 2025 Склад-Менеджер. Все права защищены.</p>
|
||||
@ -399,4 +473,4 @@
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/warehouse.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
41
js/auth.js
41
js/auth.js
@ -39,14 +39,17 @@ class AuthSystem {
|
||||
try {
|
||||
this.currentUser = JSON.parse(userData);
|
||||
this.isAuthenticated = true;
|
||||
console.log('Пользователь авторизован:', this.currentUser);
|
||||
this.updateUI();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке данных пользователя:', error);
|
||||
this.logout();
|
||||
}
|
||||
} else {
|
||||
console.log('Пользователь не авторизован');
|
||||
// Если нет сессии и мы не на странице входа, перенаправляем
|
||||
if (!window.location.pathname.includes('login.html')) {
|
||||
console.log('Перенаправление на страницу входа');
|
||||
window.location.href = 'login.html';
|
||||
}
|
||||
}
|
||||
@ -54,8 +57,8 @@ class AuthSystem {
|
||||
|
||||
// Вход в систему
|
||||
login() {
|
||||
const username = $('#username').val();
|
||||
const password = $('#password').val();
|
||||
const username = $('#username').val().trim();
|
||||
const password = $('#password').val().trim();
|
||||
|
||||
if (!username || !password) {
|
||||
this.showNotification('Пожалуйста, заполните все поля', 'warning');
|
||||
@ -77,11 +80,24 @@ class AuthSystem {
|
||||
|
||||
this.showNotification('Успешный вход в систему', 'success');
|
||||
|
||||
// Скрываем индикатор загрузки и восстанавливаем кнопку
|
||||
$('#loginLoading').hide();
|
||||
$('#loginBtn').prop('disabled', false);
|
||||
|
||||
// Перенаправляем на главную страницу
|
||||
setTimeout(() => {
|
||||
window.location.href = 'index.html';
|
||||
}, 1000);
|
||||
} else {
|
||||
// Скрываем индикатор загрузки и восстанавливаем кнопку
|
||||
$('#loginLoading').hide();
|
||||
$('#loginBtn').prop('disabled', false);
|
||||
|
||||
// Показываем ошибку в полях
|
||||
$('#username').addClass('is-invalid');
|
||||
$('#password').addClass('is-invalid');
|
||||
$('#usernameError').text('Неверное имя пользователя или пароль').show();
|
||||
|
||||
this.showNotification('Неверное имя пользователя или пароль', 'danger');
|
||||
}
|
||||
}
|
||||
@ -101,18 +117,29 @@ class AuthSystem {
|
||||
|
||||
// Обновление интерфейса в зависимости от прав доступа
|
||||
updateUI() {
|
||||
if (!this.currentUser) return;
|
||||
if (!this.currentUser) {
|
||||
console.log('Нет текущего пользователя для обновления UI');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Обновление UI для пользователя:', this.currentUser.name);
|
||||
|
||||
// Обновляем информацию о пользователе в навигации
|
||||
$('#profileDropdown .dropdown-toggle').html(`
|
||||
<i class="fas fa-user me-1"></i>${this.currentUser.name}
|
||||
`);
|
||||
const profileToggle = $('#profileDropdown .dropdown-toggle');
|
||||
if (profileToggle.length) {
|
||||
profileToggle.html(`<i class="fas fa-user me-1"></i>${this.currentUser.name}`);
|
||||
}
|
||||
|
||||
// Показываем профиль для всех авторизованных пользователей
|
||||
$('#profileDropdown').show();
|
||||
|
||||
// Показываем/скрываем элементы администрирования
|
||||
if (this.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
console.log('Показана панель администратора');
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
console.log('Скрыта панель администратора');
|
||||
}
|
||||
|
||||
// Обновляем права доступа для кнопок
|
||||
@ -188,7 +215,7 @@ class AuthSystem {
|
||||
|
||||
// Проверка аутентификации
|
||||
isUserAuthenticated() {
|
||||
return this.isAuthenticated;
|
||||
return this.isAuthenticated && this.currentUser !== null;
|
||||
}
|
||||
|
||||
// Получение списка пользователей (имитация базы данных)
|
||||
|
||||
276
js/catalog.js
276
js/catalog.js
@ -1,4 +1,4 @@
|
||||
// JavaScript для страницы каталога товаров
|
||||
// JavaScript для страницы каталога позиций
|
||||
|
||||
class CatalogManager {
|
||||
constructor() {
|
||||
@ -14,10 +14,45 @@ class CatalogManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
$(document).ready(() => {
|
||||
this.loadCatalog();
|
||||
this.bindEvents();
|
||||
this.initializeFilters();
|
||||
$(document).ready(async () => {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация каталога для пользователя:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем каталог
|
||||
await this.loadCatalog();
|
||||
|
||||
// Загружаем категории в навигацию
|
||||
await this.loadCategoriesNav();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
// Обновляем информацию о пользователе
|
||||
this.updateUserInfo();
|
||||
|
||||
// Привязываем события
|
||||
this.bindEvents();
|
||||
|
||||
// Инициализируем фильтры
|
||||
this.initializeFilters();
|
||||
|
||||
// Проверяем параметр категории в URL
|
||||
this.handleCategoryFromUrl();
|
||||
|
||||
// Обновляем права доступа
|
||||
this.updatePermissions();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -41,6 +76,13 @@ class CatalogManager {
|
||||
|
||||
// Экспорт
|
||||
$('#exportBtn').on('click', () => this.exportCatalog());
|
||||
|
||||
// Обработчик выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = 'login.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка каталога
|
||||
@ -48,12 +90,17 @@ class CatalogManager {
|
||||
try {
|
||||
this.showLoading();
|
||||
|
||||
// Загружаем товары
|
||||
// Загружаем позиции
|
||||
this.currentItems = await this.warehouse.getAllItems();
|
||||
|
||||
// Загружаем категории
|
||||
const categories = await this.warehouse.getCategories();
|
||||
this.populateCategoryFilters(categories);
|
||||
if (categories && categories.length > 0) {
|
||||
this.populateCategoryFilters(categories);
|
||||
} else {
|
||||
console.warn('Категории не найдены');
|
||||
auth.showNotification('Категории не загружены', 'warning');
|
||||
}
|
||||
|
||||
// Применяем фильтры и рендерим
|
||||
this.applyFilters();
|
||||
@ -83,6 +130,11 @@ class CatalogManager {
|
||||
|
||||
// Заполнение фильтра категорий
|
||||
populateCategoryFilters(categories) {
|
||||
if (!categories || categories.length === 0) {
|
||||
console.warn('Нет категорий для отображения');
|
||||
return;
|
||||
}
|
||||
|
||||
const categoryFilter = $('#categoryFilter');
|
||||
const addItemCategory = $('#itemCategory');
|
||||
const editItemCategory = $('#editItemCategory');
|
||||
@ -94,10 +146,12 @@ class CatalogManager {
|
||||
|
||||
// Добавляем категории
|
||||
categories.forEach(category => {
|
||||
const option = `<option value="${category.id}">${category.name}</option>`;
|
||||
categoryFilter.append(option);
|
||||
addItemCategory.append(option);
|
||||
editItemCategory.append(option);
|
||||
if (category && category.id && category.name) {
|
||||
const option = `<option value="${category.id}">${category.name}</option>`;
|
||||
categoryFilter.append(option);
|
||||
addItemCategory.append(option);
|
||||
editItemCategory.append(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -125,7 +179,7 @@ class CatalogManager {
|
||||
this.filters.inStock = $('#inStockFilter').is(':checked');
|
||||
this.filters.lowStock = $('#lowStockFilter').is(':checked');
|
||||
|
||||
// Фильтруем товары
|
||||
// Фильтруем позиции
|
||||
this.filteredItems = this.currentItems.filter(item => {
|
||||
// Поиск по названию, описанию и штрих-коду
|
||||
if (this.filters.search) {
|
||||
@ -191,7 +245,7 @@ class CatalogManager {
|
||||
<div class="col-12">
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-search"></i>
|
||||
<h4>Товары не найдены</h4>
|
||||
<h4>Позиции не найдены</h4>
|
||||
<p>Попробуйте изменить параметры поиска или фильтры</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -205,7 +259,7 @@ class CatalogManager {
|
||||
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);
|
||||
@ -216,7 +270,7 @@ class CatalogManager {
|
||||
this.renderPagination(totalPages);
|
||||
}
|
||||
|
||||
// Создание карточки товара (сетка)
|
||||
// Создание карточки позиции (сетка)
|
||||
createGridItem(item) {
|
||||
const category = this.getCategoryInfo(item.category);
|
||||
const stockStatus = this.getStockStatus(item);
|
||||
@ -248,7 +302,7 @@ class CatalogManager {
|
||||
<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') ? `
|
||||
${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
|
||||
<button class="btn btn-outline-warning btn-sm" onclick="catalogManager.editItem(${item.id})">
|
||||
<i class="fas fa-edit me-1"></i>Редактировать
|
||||
</button>
|
||||
@ -260,7 +314,7 @@ class CatalogManager {
|
||||
`;
|
||||
}
|
||||
|
||||
// Создание строки товара (список)
|
||||
// Создание строки позиции (список)
|
||||
createListItem(item) {
|
||||
const category = this.getCategoryInfo(item.category);
|
||||
const stockStatus = this.getStockStatus(item);
|
||||
@ -300,12 +354,12 @@ class CatalogManager {
|
||||
<li><a class="dropdown-item" href="#" onclick="catalogManager.viewItem(${item.id})">
|
||||
<i class="fas fa-eye me-2"></i>Просмотр
|
||||
</a></li>
|
||||
${auth.hasPermission('write') ? `
|
||||
${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
|
||||
<li><a class="dropdown-item" href="#" onclick="catalogManager.editItem(${item.id})">
|
||||
<i class="fas fa-edit me-2"></i>Редактировать
|
||||
</a></li>
|
||||
` : ''}
|
||||
${auth.hasPermission('delete') ? `
|
||||
${auth.hasRole('admin') ? `
|
||||
<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>Удалить
|
||||
@ -370,10 +424,14 @@ class CatalogManager {
|
||||
|
||||
// Обновление статистики
|
||||
updateStatistics() {
|
||||
if (!this.filteredItems) {
|
||||
this.filteredItems = [];
|
||||
}
|
||||
|
||||
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;
|
||||
const inStockItems = this.filteredItems.filter(item => item && item.quantity > 0).length;
|
||||
const lowStockItems = this.filteredItems.filter(item => item && item.quantity <= (item.minQuantity || 0)).length;
|
||||
const categories = [...new Set(this.filteredItems.filter(item => item && item.category).map(item => item.category))].length;
|
||||
|
||||
$('#totalItemsCount').text(totalItems);
|
||||
$('#inStockCount').text(inStockItems);
|
||||
@ -383,17 +441,21 @@ class CatalogManager {
|
||||
|
||||
// Получение информации о категории
|
||||
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' }
|
||||
];
|
||||
if (!categoryId) {
|
||||
return { id: 'unknown', name: 'Без категории', icon: 'fas fa-box' };
|
||||
}
|
||||
|
||||
// Используем категории из warehouse
|
||||
const category = this.warehouse.categories.find(cat => cat.id === categoryId);
|
||||
if (category) {
|
||||
return {
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
icon: category.icon || 'fas fa-box'
|
||||
};
|
||||
}
|
||||
|
||||
return categories.find(cat => cat.id === categoryId) ||
|
||||
{ id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
|
||||
return { id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
|
||||
}
|
||||
|
||||
// Получение статуса запаса
|
||||
@ -407,16 +469,16 @@ class CatalogManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Просмотр товара
|
||||
// Просмотр позиции
|
||||
viewItem(itemId) {
|
||||
const item = this.currentItems.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
// Здесь можно открыть модальное окно с подробной информацией
|
||||
auth.showNotification(`Просмотр товара: ${item.name}`, 'info');
|
||||
auth.showNotification(`Просмотр позиции: ${item.name}`, 'info');
|
||||
}
|
||||
|
||||
// Редактирование товара
|
||||
// Редактирование позиции
|
||||
async editItem(itemId) {
|
||||
const item = this.currentItems.find(i => i.id === itemId);
|
||||
if (!item) return;
|
||||
@ -467,19 +529,19 @@ class CatalogManager {
|
||||
this.currentItems[itemIndex] = { ...this.currentItems[itemIndex], ...updateData };
|
||||
}
|
||||
|
||||
auth.showNotification('Товар успешно обновлен', 'success');
|
||||
auth.showNotification('Позиция успешно обновлена', 'success');
|
||||
|
||||
// Закрываем модальное окно и обновляем каталог
|
||||
$('#editItemModal').modal('hide');
|
||||
this.applyFilters();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении товара:', error);
|
||||
auth.showNotification('Ошибка при обновлении товара', 'danger');
|
||||
console.error('Ошибка при обновлении позиции:', error);
|
||||
auth.showNotification('Ошибка при обновлении позиции', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Добавление нового товара
|
||||
// Добавление новой позиции
|
||||
async addNewItem() {
|
||||
const formData = this.getFormData('#addItemForm');
|
||||
|
||||
@ -493,7 +555,7 @@ class CatalogManager {
|
||||
// Добавляем в локальные данные
|
||||
this.currentItems.push(newItem);
|
||||
|
||||
auth.showNotification('Товар успешно добавлен', 'success');
|
||||
auth.showNotification('Позиция успешно добавлена', 'success');
|
||||
|
||||
// Закрываем модальное окно и обновляем каталог
|
||||
$('#addItemModal').modal('hide');
|
||||
@ -503,14 +565,14 @@ class CatalogManager {
|
||||
$('#addItemForm')[0].reset();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при добавлении товара:', error);
|
||||
auth.showNotification('Ошибка при добавлении товара', 'danger');
|
||||
console.error('Ошибка при добавлении позиции:', error);
|
||||
auth.showNotification('Ошибка при добавлении позиции', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление товара
|
||||
// Удаление позиции
|
||||
async deleteItem(itemId) {
|
||||
if (!confirm('Вы уверены, что хотите удалить этот товар?')) {
|
||||
if (!confirm('Вы уверены, что хотите удалить эту позицию?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -520,12 +582,12 @@ class CatalogManager {
|
||||
// Удаляем из локальных данных
|
||||
this.currentItems = this.currentItems.filter(i => i.id !== itemId);
|
||||
|
||||
auth.showNotification('Товар успешно удален', 'success');
|
||||
auth.showNotification('Позиция успешно удалена', 'success');
|
||||
this.applyFilters();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении товара:', error);
|
||||
auth.showNotification('Ошибка при удалении товара', 'danger');
|
||||
console.error('Ошибка при удалении позиции:', error);
|
||||
auth.showNotification('Ошибка при удалении позиции', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
@ -632,6 +694,126 @@ class CatalogManager {
|
||||
hideLoading() {
|
||||
// Загрузка завершается в renderCatalog()
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
console.log('Обновление информации о пользователе в каталоге:', user);
|
||||
|
||||
if (user) {
|
||||
// Обновляем имя пользователя в профиле
|
||||
const profileToggle = $('#profileDropdown .dropdown-toggle');
|
||||
if (profileToggle.length) {
|
||||
profileToggle.html(`<i class="fas fa-user me-1"></i>${user.name}`);
|
||||
}
|
||||
|
||||
// Показываем профиль для всех авторизованных пользователей
|
||||
$('#profileDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
console.log('Показана панель администратора в каталоге');
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
console.log('Скрыта панель администратора в каталоге');
|
||||
}
|
||||
} else {
|
||||
console.log('Пользователь не авторизован в каталоге');
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
async loadCategoriesNav() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const categoriesNav = $('#categoriesNav');
|
||||
|
||||
// Очищаем существующие категории (кроме "Главная")
|
||||
categoriesNav.find('li:not(:first-child)').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
categoriesNav.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление прав доступа
|
||||
updatePermissions() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (!user) return;
|
||||
|
||||
// Показываем кнопку добавления для администраторов и менеджеров
|
||||
if (auth.hasRole('admin') || auth.hasRole('manager')) {
|
||||
$('#addItemBtn').show();
|
||||
} else {
|
||||
$('#addItemBtn').hide();
|
||||
}
|
||||
|
||||
// Показываем кнопку экспорта для всех авторизованных пользователей
|
||||
$('#exportBtn').show();
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка параметра категории из URL
|
||||
handleCategoryFromUrl() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const categoryParam = urlParams.get('category');
|
||||
|
||||
if (categoryParam) {
|
||||
// Устанавливаем фильтр категории
|
||||
$('#categoryFilter').val(categoryParam);
|
||||
|
||||
// Применяем фильтры
|
||||
this.applyFilters();
|
||||
|
||||
// Показываем уведомление о примененном фильтре
|
||||
const categoryInfo = this.getCategoryInfo(categoryParam);
|
||||
auth.showNotification(`Отфильтровано по категории: ${categoryInfo.name}`, 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера каталога
|
||||
|
||||
403
js/categories.js
Normal file
403
js/categories.js
Normal file
@ -0,0 +1,403 @@
|
||||
// JavaScript для управления категориями
|
||||
class CategoryManager {
|
||||
constructor() {
|
||||
this.warehouse = new WarehouseManager();
|
||||
this.categories = [];
|
||||
this.currentCategory = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = '../login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем права доступа
|
||||
if (!auth.hasRole('admin')) {
|
||||
console.log('Пользователь не имеет прав администратора');
|
||||
auth.showNotification('Недостаточно прав для доступа к этой странице', 'danger');
|
||||
window.location.href = '../index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация управления категориями для администратора:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем данные
|
||||
await this.loadCategories();
|
||||
this.updateStatistics();
|
||||
this.renderCategoriesTable();
|
||||
this.bindEvents();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
this.updateUserInfo();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий
|
||||
async loadCategories() {
|
||||
this.categories = await this.warehouse.getCategories();
|
||||
}
|
||||
|
||||
// Обновление статистики
|
||||
updateStatistics() {
|
||||
const totalCategories = this.categories.length;
|
||||
const activeCategories = this.categories.filter(cat => cat.active).length;
|
||||
const emptyCategories = this.categories.filter(cat => cat.itemCount === 0).length;
|
||||
|
||||
// Получаем общее количество позиций
|
||||
const allItems = this.warehouse.getAllItemsSync();
|
||||
const totalItems = allItems.length;
|
||||
|
||||
$('#totalCategories').text(totalCategories);
|
||||
$('#totalItems').text(totalItems);
|
||||
$('#activeCategories').text(activeCategories);
|
||||
$('#emptyCategories').text(emptyCategories);
|
||||
}
|
||||
|
||||
// Рендеринг таблицы категорий
|
||||
renderCategoriesTable() {
|
||||
const tbody = $('#categoriesTable tbody');
|
||||
tbody.empty();
|
||||
|
||||
if (this.categories.length === 0) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4">
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-tags fa-3x text-muted mb-3"></i>
|
||||
<h5>Категории не найдены</h5>
|
||||
<p class="text-muted">Добавьте первую категорию для начала работы</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.categories.forEach(category => {
|
||||
const row = this.createCategoryRow(category);
|
||||
tbody.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Создание строки категории
|
||||
createCategoryRow(category) {
|
||||
const statusBadge = category.active ?
|
||||
'<span class="badge bg-success">Активна</span>' :
|
||||
'<span class="badge bg-secondary">Неактивна</span>';
|
||||
|
||||
const iconPreview = category.icon ?
|
||||
`<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>` :
|
||||
'<i class="fas fa-tag text-muted"></i>';
|
||||
|
||||
return `
|
||||
<tr data-category-id="${category.id}">
|
||||
<td>${category.id}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2">${iconPreview}</span>
|
||||
<strong>${category.name}</strong>
|
||||
</div>
|
||||
</td>
|
||||
<td>${category.description || '-'}</td>
|
||||
<td>
|
||||
<code>${category.icon || 'fas fa-tag'}</code>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">${category.itemCount || 0}</span>
|
||||
</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary" onclick="categoryManager.editCategory('${category.id}')" title="Редактировать">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger" onclick="categoryManager.deleteCategory('${category.id}')" title="Удалить">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
// Привязка событий
|
||||
bindEvents() {
|
||||
// Добавление категории
|
||||
$('#saveCategoryBtn').on('click', () => this.addCategory());
|
||||
|
||||
// Редактирование категории
|
||||
$('#updateCategoryBtn').on('click', () => this.updateCategory());
|
||||
|
||||
// Очистка формы при закрытии модального окна
|
||||
$('#addCategoryModal').on('hidden.bs.modal', () => this.clearAddForm());
|
||||
$('#editCategoryModal').on('hidden.bs.modal', () => this.clearEditForm());
|
||||
|
||||
// Валидация формы
|
||||
$('#addCategoryForm').on('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.addCategory();
|
||||
});
|
||||
|
||||
$('#editCategoryForm').on('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.updateCategory();
|
||||
});
|
||||
}
|
||||
|
||||
// Добавление категории
|
||||
async addCategory() {
|
||||
try {
|
||||
const formData = this.getAddFormData();
|
||||
|
||||
if (!formData.name.trim()) {
|
||||
auth.showNotification('Введите название категории', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем уникальность названия
|
||||
const existingCategory = this.categories.find(cat =>
|
||||
cat.name.toLowerCase() === formData.name.toLowerCase()
|
||||
);
|
||||
|
||||
if (existingCategory) {
|
||||
auth.showNotification('Категория с таким названием уже существует', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const newCategory = await this.warehouse.addCategory(formData);
|
||||
this.categories.push(newCategory);
|
||||
|
||||
this.updateStatistics();
|
||||
this.renderCategoriesTable();
|
||||
|
||||
$('#addCategoryModal').modal('hide');
|
||||
auth.showNotification('Категория успешно добавлена', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при добавлении категории:', error);
|
||||
auth.showNotification('Ошибка при добавлении категории', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Редактирование категории
|
||||
async editCategory(categoryId) {
|
||||
try {
|
||||
const category = this.categories.find(cat => cat.id === categoryId);
|
||||
if (!category) {
|
||||
auth.showNotification('Категория не найдена', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentCategory = category;
|
||||
this.fillEditForm(category);
|
||||
$('#editCategoryModal').modal('show');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при редактировании категории:', error);
|
||||
auth.showNotification('Ошибка при загрузке категории', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление категории
|
||||
async updateCategory() {
|
||||
try {
|
||||
if (!this.currentCategory) {
|
||||
auth.showNotification('Категория не выбрана', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = this.getEditFormData();
|
||||
|
||||
if (!formData.name.trim()) {
|
||||
auth.showNotification('Введите название категории', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем уникальность названия (исключая текущую категорию)
|
||||
const existingCategory = this.categories.find(cat =>
|
||||
cat.id !== this.currentCategory.id &&
|
||||
cat.name.toLowerCase() === formData.name.toLowerCase()
|
||||
);
|
||||
|
||||
if (existingCategory) {
|
||||
auth.showNotification('Категория с таким названием уже существует', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedCategory = await this.warehouse.updateCategory(this.currentCategory.id, formData);
|
||||
|
||||
// Обновляем категорию в списке
|
||||
const index = this.categories.findIndex(cat => cat.id === this.currentCategory.id);
|
||||
if (index !== -1) {
|
||||
this.categories[index] = updatedCategory;
|
||||
}
|
||||
|
||||
this.updateStatistics();
|
||||
this.renderCategoriesTable();
|
||||
|
||||
$('#editCategoryModal').modal('hide');
|
||||
auth.showNotification('Категория успешно обновлена', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении категории:', error);
|
||||
auth.showNotification('Ошибка при обновлении категории', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление категории
|
||||
async deleteCategory(categoryId) {
|
||||
try {
|
||||
const category = this.categories.find(cat => cat.id === categoryId);
|
||||
if (!category) {
|
||||
auth.showNotification('Категория не найдена', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, есть ли позиции в этой категории
|
||||
if (category.itemCount > 0) {
|
||||
const confirmDelete = confirm(
|
||||
`В категории "${category.name}" есть ${category.itemCount} позиций. ` +
|
||||
'Удаление категории приведет к удалению всех позиций. Продолжить?'
|
||||
);
|
||||
|
||||
if (!confirmDelete) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const confirmDelete = confirm(`Вы уверены, что хотите удалить категорию "${category.name}"?`);
|
||||
if (!confirmDelete) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await this.warehouse.deleteCategory(categoryId);
|
||||
|
||||
// Удаляем категорию из списка
|
||||
this.categories = this.categories.filter(cat => cat.id !== categoryId);
|
||||
|
||||
this.updateStatistics();
|
||||
this.renderCategoriesTable();
|
||||
|
||||
auth.showNotification('Категория успешно удалена', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении категории:', error);
|
||||
auth.showNotification('Ошибка при удалении категории', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Получение данных формы добавления
|
||||
getAddFormData() {
|
||||
return {
|
||||
name: $('#categoryName').val().trim(),
|
||||
description: $('#categoryDescription').val().trim(),
|
||||
icon: $('#categoryIcon').val().trim() || 'fas fa-tag',
|
||||
color: $('#categoryColor').val(),
|
||||
active: $('#categoryActive').is(':checked')
|
||||
};
|
||||
}
|
||||
|
||||
// Получение данных формы редактирования
|
||||
getEditFormData() {
|
||||
return {
|
||||
name: $('#editCategoryName').val().trim(),
|
||||
description: $('#editCategoryDescription').val().trim(),
|
||||
icon: $('#editCategoryIcon').val().trim() || 'fas fa-tag',
|
||||
color: $('#editCategoryColor').val(),
|
||||
active: $('#editCategoryActive').is(':checked')
|
||||
};
|
||||
}
|
||||
|
||||
// Заполнение формы редактирования
|
||||
fillEditForm(category) {
|
||||
$('#editCategoryId').val(category.id);
|
||||
$('#editCategoryName').val(category.name);
|
||||
$('#editCategoryDescription').val(category.description || '');
|
||||
$('#editCategoryIcon').val(category.icon || 'fas fa-tag');
|
||||
$('#editCategoryColor').val(category.color || '#2c5aa0');
|
||||
$('#editCategoryActive').prop('checked', category.active);
|
||||
}
|
||||
|
||||
// Очистка формы добавления
|
||||
clearAddForm() {
|
||||
$('#addCategoryForm')[0].reset();
|
||||
$('#categoryColor').val('#2c5aa0');
|
||||
$('#categoryActive').prop('checked', true);
|
||||
}
|
||||
|
||||
// Очистка формы редактирования
|
||||
clearEditForm() {
|
||||
$('#editCategoryForm')[0].reset();
|
||||
$('#editCategoryColor').val('#2c5aa0');
|
||||
this.currentCategory = null;
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (user) {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="../catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация при загрузке страницы
|
||||
let categoryManager;
|
||||
|
||||
$(document).ready(() => {
|
||||
categoryManager = new CategoryManager();
|
||||
|
||||
// Обработка выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = '../login.html';
|
||||
});
|
||||
});
|
||||
126
js/history.js
126
js/history.js
@ -13,10 +13,39 @@ class HistoryManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
$(document).ready(() => {
|
||||
this.loadHistory();
|
||||
this.bindEvents();
|
||||
this.initializeFilters();
|
||||
$(document).ready(async () => {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация истории для пользователя:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем историю операций
|
||||
await this.loadHistory();
|
||||
|
||||
// Загружаем категории в навигацию
|
||||
await this.loadCategoriesNav();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
// Обновляем информацию о пользователе
|
||||
this.updateUserInfo();
|
||||
|
||||
// Привязываем события
|
||||
this.bindEvents();
|
||||
|
||||
// Инициализируем фильтры
|
||||
this.initializeFilters();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -32,6 +61,13 @@ class HistoryManager {
|
||||
$('#exportBtn').on('click', () => this.exportOperations());
|
||||
$('#generateReportBtn').on('click', () => this.generateReport());
|
||||
|
||||
// Обработчик выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = 'login.html';
|
||||
});
|
||||
|
||||
// Установка дат по умолчанию
|
||||
this.setDefaultDates();
|
||||
}
|
||||
@ -44,7 +80,7 @@ class HistoryManager {
|
||||
// Загружаем операции
|
||||
this.operations = await this.warehouse.getOperations();
|
||||
|
||||
// Загружаем товары для фильтра
|
||||
// Загружаем позиции для фильтра
|
||||
const items = await this.warehouse.getAllItems();
|
||||
this.populateItemFilter(items);
|
||||
|
||||
@ -89,7 +125,7 @@ class HistoryManager {
|
||||
$('#reportEndDate').val(today.toISOString().split('T')[0]);
|
||||
}
|
||||
|
||||
// Заполнение фильтра товаров
|
||||
// Заполнение фильтра позиций
|
||||
populateItemFilter(items) {
|
||||
const itemFilter = $('#itemFilter');
|
||||
const reportItem = $('#reportItem');
|
||||
@ -147,7 +183,7 @@ class HistoryManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Фильтр по товару
|
||||
// Фильтр по позиции
|
||||
if (this.filters.itemId && operation.itemId !== parseInt(this.filters.itemId)) {
|
||||
return false;
|
||||
}
|
||||
@ -405,10 +441,10 @@ class HistoryManager {
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Детали товара</h6>
|
||||
<h6>Детали позиции</h6>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<td><strong>Товар:</strong></td>
|
||||
<td><strong>Позиция:</strong></td>
|
||||
<td>${operation.itemName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -614,6 +650,78 @@ class HistoryManager {
|
||||
hideLoading() {
|
||||
// Загрузка завершается в renderOperations()
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (user) {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
async loadCategoriesNav() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const categoriesNav = $('#categoriesNav');
|
||||
|
||||
// Очищаем существующие категории (кроме "Главная")
|
||||
categoriesNav.find('li:not(:first-child)').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
categoriesNav.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера истории
|
||||
|
||||
149
js/main.js
149
js/main.js
@ -8,10 +8,44 @@ class MainApp {
|
||||
|
||||
init() {
|
||||
// Инициализация после загрузки DOM
|
||||
$(document).ready(() => {
|
||||
this.loadDashboardData();
|
||||
this.bindEvents();
|
||||
this.initializeModals();
|
||||
$(document).ready(async () => {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация главной страницы для пользователя:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем данные дашборда
|
||||
await this.loadDashboardData();
|
||||
|
||||
// Загружаем категории в навигацию
|
||||
await this.loadCategoriesNav();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
// Обновляем информацию о пользователе
|
||||
this.updateUserInfo();
|
||||
|
||||
// Принудительно обновляем еще раз через небольшую задержку
|
||||
setTimeout(() => {
|
||||
this.updateUserInfo();
|
||||
}, 500);
|
||||
|
||||
// Привязываем события
|
||||
this.bindEvents();
|
||||
|
||||
// Инициализируем модальные окна
|
||||
this.initializeModals();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,6 +63,13 @@ class MainApp {
|
||||
|
||||
// Обработчики для экспорта
|
||||
$('#exportBtn').on('click', () => this.exportData());
|
||||
|
||||
// Обработчик выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = 'login.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка данных для дашборда
|
||||
@ -38,14 +79,14 @@ class MainApp {
|
||||
this.showLoading();
|
||||
|
||||
// Загружаем статистику
|
||||
const stats = await this.warehouse.getStatistics();
|
||||
const stats = this.warehouse.getStatistics();
|
||||
this.updateStatistics(stats);
|
||||
|
||||
// Загружаем последние операции
|
||||
const operations = await this.warehouse.getRecentOperations(10);
|
||||
this.updateOperationsTable(operations);
|
||||
|
||||
// Загружаем товары для модальных окон
|
||||
// Загружаем позиции для модальных окон
|
||||
const items = await this.warehouse.getAllItems();
|
||||
this.populateItemSelects(items);
|
||||
|
||||
@ -146,7 +187,7 @@ class MainApp {
|
||||
});
|
||||
}
|
||||
|
||||
// Заполнение селектов товаров
|
||||
// Заполнение селектов позиций
|
||||
populateItemSelects(items) {
|
||||
const selects = ['#incomingItem', '#outgoingItem'];
|
||||
|
||||
@ -160,7 +201,7 @@ class MainApp {
|
||||
});
|
||||
}
|
||||
|
||||
// Добавление нового товара
|
||||
// Добавление новой позиции
|
||||
async addNewItem() {
|
||||
const formData = this.getFormData('#addItemForm');
|
||||
|
||||
@ -170,7 +211,7 @@ class MainApp {
|
||||
|
||||
try {
|
||||
const newItem = await this.warehouse.addItem(formData);
|
||||
auth.showNotification('Товар успешно добавлен', 'success');
|
||||
auth.showNotification('Позиция успешно добавлена', 'success');
|
||||
|
||||
// Закрываем модальное окно
|
||||
$('#addItemModal').modal('hide');
|
||||
@ -182,8 +223,8 @@ class MainApp {
|
||||
$('#addItemForm')[0].reset();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при добавлении товара:', error);
|
||||
auth.showNotification('Ошибка при добавлении товара', 'danger');
|
||||
console.error('Ошибка при добавлении позиции:', error);
|
||||
auth.showNotification('Ошибка при добавлении позиции', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,6 +465,92 @@ class MainApp {
|
||||
hideLoading() {
|
||||
$('#loadingOverlay').remove();
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
console.log('Обновление информации о пользователе:', user);
|
||||
|
||||
if (user) {
|
||||
// Обновляем имя пользователя в профиле
|
||||
const profileToggle = $('#profileDropdown .dropdown-toggle');
|
||||
if (profileToggle.length) {
|
||||
profileToggle.html(`<i class="fas fa-user me-1"></i>${user.name}`);
|
||||
}
|
||||
|
||||
// Показываем профиль для всех авторизованных пользователей
|
||||
$('#profileDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
console.log('Показана панель администратора');
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
console.log('Скрыта панель администратора');
|
||||
}
|
||||
} else {
|
||||
console.log('Пользователь не авторизован');
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
async loadCategoriesNav() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const categoriesNav = $('#categoriesNav');
|
||||
|
||||
// Очищаем существующие категории (кроме "Главная")
|
||||
categoriesNav.find('li:not(:first-child)').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
categoriesNav.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация приложения
|
||||
|
||||
112
js/profile.js
112
js/profile.js
@ -10,9 +10,36 @@ class ProfileManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
$(document).ready(() => {
|
||||
this.loadProfile();
|
||||
this.bindEvents();
|
||||
$(document).ready(async () => {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация профиля для пользователя:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем профиль
|
||||
await this.loadProfile();
|
||||
|
||||
// Загружаем категории в навигацию
|
||||
await this.loadCategoriesNav();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
// Обновляем информацию о пользователе
|
||||
this.updateUserInfo();
|
||||
|
||||
// Привязываем события
|
||||
this.bindEvents();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,6 +57,13 @@ class ProfileManager {
|
||||
e.preventDefault();
|
||||
this.saveProfile();
|
||||
});
|
||||
|
||||
// Обработчик выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = 'login.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка профиля
|
||||
@ -427,6 +461,78 @@ class ProfileManager {
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (user) {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
async loadCategoriesNav() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const categoriesNav = $('#categoriesNav');
|
||||
|
||||
// Очищаем существующие категории (кроме "Главная")
|
||||
categoriesNav.find('li:not(:first-child)').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
categoriesNav.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера профиля
|
||||
|
||||
134
js/search.js
134
js/search.js
@ -16,10 +16,39 @@ class SearchManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
$(document).ready(() => {
|
||||
this.loadCategories();
|
||||
this.bindEvents();
|
||||
this.initializeFilters();
|
||||
$(document).ready(async () => {
|
||||
try {
|
||||
// Проверяем авторизацию
|
||||
if (!auth.isUserAuthenticated()) {
|
||||
console.log('Пользователь не авторизован, перенаправление на login.html');
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Инициализация поиска для пользователя:', auth.getCurrentUser()?.name);
|
||||
|
||||
// Загружаем категории
|
||||
await this.loadCategories();
|
||||
|
||||
// Загружаем категории в навигацию
|
||||
await this.loadCategoriesNav();
|
||||
|
||||
// Загружаем категории в выпадающее меню
|
||||
await this.loadCategoriesDropdown();
|
||||
|
||||
// Обновляем информацию о пользователе
|
||||
this.updateUserInfo();
|
||||
|
||||
// Привязываем события
|
||||
this.bindEvents();
|
||||
|
||||
// Инициализируем фильтры
|
||||
this.initializeFilters();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации:', error);
|
||||
auth.showNotification('Ошибка загрузки данных', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,6 +74,13 @@ class SearchManager {
|
||||
e.preventDefault();
|
||||
$(e.target).tab('show');
|
||||
});
|
||||
|
||||
// Обработчик выхода
|
||||
$('#logoutBtn').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
auth.logout();
|
||||
window.location.href = 'login.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка категорий для фильтра
|
||||
@ -113,7 +149,7 @@ class SearchManager {
|
||||
this.filters.endDate = $('#endDate').val();
|
||||
}
|
||||
|
||||
// Поиск товаров
|
||||
// Поиск позиций
|
||||
async searchItems(query) {
|
||||
if (this.filters.searchType === 'operations') {
|
||||
this.searchResults.items = [];
|
||||
@ -218,7 +254,7 @@ class SearchManager {
|
||||
<div class="col-12">
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-box-open"></i>
|
||||
<h5>Товары не найдены</h5>
|
||||
<h5>Позиции не найдены</h5>
|
||||
<p>Попробуйте изменить поисковый запрос</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -226,7 +262,7 @@ class SearchManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Пагинация для товаров
|
||||
// Пагинация для позиций
|
||||
const totalPages = Math.ceil(this.searchResults.items.length / this.resultsPerPage);
|
||||
const startIndex = (this.currentPage - 1) * this.resultsPerPage;
|
||||
const endIndex = startIndex + this.resultsPerPage;
|
||||
@ -238,7 +274,7 @@ class SearchManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Создание карточки товара
|
||||
// Создание карточки позиции
|
||||
createItemCard(item) {
|
||||
const category = this.getCategoryInfo(item.category);
|
||||
const stockStatus = this.getStockStatus(item);
|
||||
@ -404,7 +440,7 @@ class SearchManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Сортировка товаров
|
||||
// Сортировка позиций
|
||||
this.searchResults.items.sort((a, b) => {
|
||||
let aValue = a[sortBy];
|
||||
let bValue = b[sortBy];
|
||||
@ -475,7 +511,7 @@ class SearchManager {
|
||||
csv += `Запрос: ${this.currentQuery}\n`;
|
||||
csv += `Дата поиска: ${new Date().toLocaleDateString('ru-RU')}\n\n`;
|
||||
|
||||
// Экспорт товаров
|
||||
// Экспорт позиций
|
||||
if (this.searchResults.items.length > 0) {
|
||||
csv += 'ТОВАРЫ\n';
|
||||
csv += 'ID,Название,Категория,Цена,Количество,Описание\n';
|
||||
@ -513,7 +549,7 @@ class SearchManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Просмотр деталей товара
|
||||
// Просмотр деталей позиции
|
||||
async viewItemDetails(itemId) {
|
||||
try {
|
||||
const item = await this.warehouse.getItemById(itemId);
|
||||
@ -587,8 +623,8 @@ class SearchManager {
|
||||
$('#itemDetailsModal').modal('show');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке деталей товара:', error);
|
||||
auth.showNotification('Ошибка при загрузке деталей товара', 'danger');
|
||||
console.error('Ошибка при загрузке деталей позиции:', error);
|
||||
auth.showNotification('Ошибка при загрузке деталей позиции', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
@ -661,6 +697,78 @@ class SearchManager {
|
||||
hideLoading() {
|
||||
$('#loadingState').hide();
|
||||
}
|
||||
|
||||
// Обновление информации о пользователе
|
||||
updateUserInfo() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (user) {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
async loadCategoriesNav() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const categoriesNav = $('#categoriesNav');
|
||||
|
||||
// Очищаем существующие категории (кроме "Главная")
|
||||
categoriesNav.find('li:not(:first-child)').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
categoriesNav.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
async loadCategoriesDropdown() {
|
||||
try {
|
||||
const categories = await this.warehouse.getCategories();
|
||||
const dropdown = $('#catalogCategoriesDropdown');
|
||||
|
||||
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
|
||||
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
|
||||
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const categoryLink = `
|
||||
<li>
|
||||
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
${category.name}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера поиска
|
||||
|
||||
198
js/warehouse.js
198
js/warehouse.js
@ -7,7 +7,7 @@ class WarehouseManager {
|
||||
this.categories = this.loadCategories();
|
||||
}
|
||||
|
||||
// Загрузка товаров из localStorage
|
||||
// Загрузка позиций из localStorage
|
||||
loadItems() {
|
||||
const items = localStorage.getItem('warehouse_items');
|
||||
return items ? JSON.parse(items) : this.getDefaultItems();
|
||||
@ -21,16 +21,88 @@ class WarehouseManager {
|
||||
|
||||
// Загрузка категорий
|
||||
loadCategories() {
|
||||
const categories = localStorage.getItem('warehouse_categories');
|
||||
if (categories) {
|
||||
this.categories = JSON.parse(categories);
|
||||
} else {
|
||||
// Загружаем категории по умолчанию
|
||||
this.categories = this.getDefaultCategories();
|
||||
this.saveCategories();
|
||||
}
|
||||
|
||||
// Обновляем количество позиций в каждой категории
|
||||
this.updateCategoryItemCounts();
|
||||
}
|
||||
|
||||
// Получение категорий по умолчанию
|
||||
getDefaultCategories() {
|
||||
return [
|
||||
{ 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' }
|
||||
{
|
||||
id: 'electronics',
|
||||
name: 'Электроника',
|
||||
description: 'Электронные компоненты и устройства',
|
||||
icon: 'fas fa-microchip',
|
||||
color: '#2c5aa0',
|
||||
active: true,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
name: 'Компоненты',
|
||||
description: 'Радиодетали и электронные компоненты',
|
||||
icon: 'fas fa-cogs',
|
||||
color: '#28a745',
|
||||
active: true,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 'tools',
|
||||
name: 'Инструменты',
|
||||
description: 'Ручной и электроинструмент',
|
||||
icon: 'fas fa-tools',
|
||||
color: '#ffc107',
|
||||
active: true,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 'office',
|
||||
name: 'Офисные принадлежности',
|
||||
description: 'Канцелярские товары и офисное оборудование',
|
||||
icon: 'fas fa-briefcase',
|
||||
color: '#6c757d',
|
||||
active: true,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 'furniture',
|
||||
name: 'Мебель',
|
||||
description: 'Офисная мебель и оборудование',
|
||||
icon: 'fas fa-chair',
|
||||
color: '#8b4513',
|
||||
active: true,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Сохранение категорий
|
||||
saveCategories() {
|
||||
localStorage.setItem('warehouse_categories', JSON.stringify(this.categories));
|
||||
}
|
||||
|
||||
// Обновление количества позиций в категориях
|
||||
updateCategoryItemCounts() {
|
||||
this.categories.forEach(category => {
|
||||
category.itemCount = this.items.filter(item => item.category === category.id).length;
|
||||
});
|
||||
this.saveCategories();
|
||||
}
|
||||
|
||||
// Сохранение данных в localStorage
|
||||
saveItems() {
|
||||
localStorage.setItem('warehouse_items', JSON.stringify(this.items));
|
||||
@ -40,7 +112,7 @@ class WarehouseManager {
|
||||
localStorage.setItem('warehouse_operations', JSON.stringify(this.operations));
|
||||
}
|
||||
|
||||
// Получение товаров по умолчанию
|
||||
// Получение позиций по умолчанию
|
||||
getDefaultItems() {
|
||||
return [
|
||||
{
|
||||
@ -54,8 +126,8 @@ class WarehouseManager {
|
||||
minQuantity: 5,
|
||||
location: 'Стеллаж A-1',
|
||||
supplier: 'Dell Inc.',
|
||||
createdAt: '2024-01-15T10:00:00Z',
|
||||
updatedAt: '2024-01-15T10:00:00Z'
|
||||
createdAt: '2025-01-15T10:00:00Z',
|
||||
updatedAt: '2025-01-15T10:00:00Z'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -68,13 +140,13 @@ class WarehouseManager {
|
||||
minQuantity: 3,
|
||||
location: 'Стеллаж A-2',
|
||||
supplier: 'HP Inc.',
|
||||
createdAt: '2024-01-10T14:30:00Z',
|
||||
updatedAt: '2024-01-10T14:30:00Z'
|
||||
createdAt: '2025-01-10T14:30:00Z',
|
||||
updatedAt: '2025-01-10T14:30:00Z'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Офисный стул',
|
||||
category: 'furniture',
|
||||
category: 'office',
|
||||
price: 3500,
|
||||
quantity: 25,
|
||||
description: 'Офисный стул с подлокотниками, черный',
|
||||
@ -82,8 +154,8 @@ class WarehouseManager {
|
||||
minQuantity: 10,
|
||||
location: 'Стеллаж B-1',
|
||||
supplier: 'МебельСтрой',
|
||||
createdAt: '2024-01-05T09:15:00Z',
|
||||
updatedAt: '2024-01-05T09:15:00Z'
|
||||
createdAt: '2025-01-05T09:15:00Z',
|
||||
updatedAt: '2025-01-05T09:15:00Z'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
@ -96,8 +168,8 @@ class WarehouseManager {
|
||||
minQuantity: 20,
|
||||
location: 'Стеллаж C-1',
|
||||
supplier: 'ОфисМаркет',
|
||||
createdAt: '2024-01-12T11:45:00Z',
|
||||
updatedAt: '2024-01-12T11:45:00Z'
|
||||
createdAt: '2025-01-12T11:45:00Z',
|
||||
updatedAt: '2025-01-12T11:45:00Z'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
@ -110,14 +182,14 @@ class WarehouseManager {
|
||||
minQuantity: 15,
|
||||
location: 'Стеллаж D-1',
|
||||
supplier: 'ИнструментСтрой',
|
||||
createdAt: '2024-01-08T16:20:00Z',
|
||||
updatedAt: '2024-01-08T16:20:00Z'
|
||||
createdAt: '2025-01-08T16:20:00Z',
|
||||
updatedAt: '2025-01-08T16:20:00Z'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Получение статистики
|
||||
async getStatistics() {
|
||||
getStatistics() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
const totalItems = this.items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
@ -137,17 +209,22 @@ class WarehouseManager {
|
||||
};
|
||||
}
|
||||
|
||||
// Получение всех товаров
|
||||
// Получение всех позиций
|
||||
async getAllItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
// Получение товара по ID
|
||||
// Синхронное получение всех позиций
|
||||
getAllItemsSync() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
// Получение позиции по ID
|
||||
async getItemById(id) {
|
||||
return this.items.find(item => item.id === parseInt(id));
|
||||
}
|
||||
|
||||
// Добавление нового товара
|
||||
// Добавление новой позиции
|
||||
async addItem(itemData) {
|
||||
const newItem = {
|
||||
id: this.getNextItemId(),
|
||||
@ -167,22 +244,22 @@ class WarehouseManager {
|
||||
this.items.push(newItem);
|
||||
this.saveItems();
|
||||
|
||||
// Создаем операцию прихода для нового товара
|
||||
// Создаем операцию прихода для новой позиции
|
||||
await this.createOperation({
|
||||
type: 'incoming',
|
||||
itemId: newItem.id,
|
||||
quantity: newItem.quantity,
|
||||
notes: 'Добавление нового товара'
|
||||
notes: 'Добавление новой позиции'
|
||||
});
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
// Обновление товара
|
||||
// Обновление позиции
|
||||
async updateItem(id, itemData) {
|
||||
const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
|
||||
if (itemIndex === -1) {
|
||||
throw new Error('Товар не найден');
|
||||
throw new Error('Позиция не найдена');
|
||||
}
|
||||
|
||||
this.items[itemIndex] = {
|
||||
@ -195,11 +272,11 @@ class WarehouseManager {
|
||||
return this.items[itemIndex];
|
||||
}
|
||||
|
||||
// Удаление товара
|
||||
// Удаление позиции
|
||||
async deleteItem(id) {
|
||||
const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
|
||||
if (itemIndex === -1) {
|
||||
throw new Error('Товар не найден');
|
||||
throw new Error('Позиция не найдена');
|
||||
}
|
||||
|
||||
this.items.splice(itemIndex, 1);
|
||||
@ -214,10 +291,10 @@ class WarehouseManager {
|
||||
|
||||
const item = await this.getItemById(itemId);
|
||||
if (!item) {
|
||||
throw new Error('Товар не найден');
|
||||
throw new Error('Позиция не найдена');
|
||||
}
|
||||
|
||||
// Обновляем количество товара
|
||||
// Обновляем количество позиции
|
||||
item.quantity += quantity;
|
||||
item.updatedAt = new Date().toISOString();
|
||||
this.saveItems();
|
||||
@ -241,15 +318,15 @@ class WarehouseManager {
|
||||
|
||||
const item = await this.getItemById(itemId);
|
||||
if (!item) {
|
||||
throw new Error('Товар не найден');
|
||||
throw new Error('Позиция не найдена');
|
||||
}
|
||||
|
||||
// Проверяем достаточность товара
|
||||
// Проверяем достаточность позиции
|
||||
if (item.quantity < quantity) {
|
||||
throw new Error(`Недостаточно товара. Доступно: ${item.quantity} шт.`);
|
||||
throw new Error(`Недостаточно позиций. Доступно: ${item.quantity} шт.`);
|
||||
}
|
||||
|
||||
// Обновляем количество товара
|
||||
// Обновляем количество позиции
|
||||
item.quantity -= quantity;
|
||||
item.updatedAt = new Date().toISOString();
|
||||
this.saveItems();
|
||||
@ -401,18 +478,63 @@ class WarehouseManager {
|
||||
return this.categories;
|
||||
}
|
||||
|
||||
// Получение категории по ID
|
||||
async getCategoryById(id) {
|
||||
return this.categories.find(cat => cat.id === id);
|
||||
}
|
||||
|
||||
// Добавление категории
|
||||
async addCategory(categoryData) {
|
||||
const newCategory = {
|
||||
id: categoryData.id || this.generateCategoryId(),
|
||||
name: categoryData.name,
|
||||
icon: categoryData.icon || 'fas fa-box'
|
||||
id: this.generateCategoryId(),
|
||||
...categoryData,
|
||||
itemCount: 0,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.categories.push(newCategory);
|
||||
this.saveCategories();
|
||||
return newCategory;
|
||||
}
|
||||
|
||||
// Обновление категории
|
||||
async updateCategory(id, categoryData) {
|
||||
const categoryIndex = this.categories.findIndex(cat => cat.id === id);
|
||||
if (categoryIndex === -1) {
|
||||
throw new Error('Категория не найдена');
|
||||
}
|
||||
|
||||
this.categories[categoryIndex] = {
|
||||
...this.categories[categoryIndex],
|
||||
...categoryData,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.saveCategories();
|
||||
return this.categories[categoryIndex];
|
||||
}
|
||||
|
||||
// Удаление категории
|
||||
async deleteCategory(id) {
|
||||
const categoryIndex = this.categories.findIndex(cat => cat.id === id);
|
||||
if (categoryIndex === -1) {
|
||||
throw new Error('Категория не найдена');
|
||||
}
|
||||
|
||||
// Удаляем все позиции этой категории
|
||||
this.items = this.items.filter(item => item.category !== id);
|
||||
this.saveItems();
|
||||
|
||||
// Удаляем категорию
|
||||
this.categories.splice(categoryIndex, 1);
|
||||
this.saveCategories();
|
||||
|
||||
// Обновляем количество позиций в остальных категориях
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Генерация следующего ID для товара
|
||||
getNextItemId() {
|
||||
const maxId = Math.max(...this.items.map(item => item.id), 0);
|
||||
|
||||
88
login.html
88
login.html
@ -8,8 +8,8 @@
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
<div class="login-container flex-grow-1">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<i class="fas fa-warehouse fa-3x mb-3"></i>
|
||||
@ -17,22 +17,30 @@
|
||||
<p class="mb-0">Система управления складом</p>
|
||||
</div>
|
||||
<div class="login-body">
|
||||
<form id="loginForm">
|
||||
<form id="loginForm" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">
|
||||
<i class="fas fa-user me-2"></i>Имя пользователя
|
||||
</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
<input type="text" class="form-control" id="username" required
|
||||
placeholder="Введите имя пользователя" autocomplete="username">
|
||||
<div class="invalid-feedback" id="usernameError"></div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="password" class="form-label">
|
||||
<i class="fas fa-lock me-2"></i>Пароль
|
||||
</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
<input type="password" class="form-control" id="password" required
|
||||
placeholder="Введите пароль" autocomplete="current-password">
|
||||
<div class="invalid-feedback" id="passwordError"></div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 mb-3">
|
||||
<button type="submit" class="btn btn-primary w-100 mb-3" id="loginBtn">
|
||||
<i class="fas fa-sign-in-alt me-2"></i>Войти в систему
|
||||
</button>
|
||||
<div id="loginLoading" class="text-center" style="display: none;">
|
||||
<div class="loading-spinner text-primary mb-2"></div>
|
||||
<small class="text-muted">Выполняется вход...</small>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Информация о тестовых пользователях -->
|
||||
@ -100,9 +108,77 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-3 mt-auto">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<small>
|
||||
<i class="fas fa-shield-alt me-2"></i>Безопасная система авторизации
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">© 2025 Склад-Менеджер. Все права защищены.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Скрипты -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="js/auth.js"></script>
|
||||
|
||||
<!-- Дополнительный JavaScript для улучшения UX -->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Обработка формы входа с улучшенной валидацией
|
||||
$('#loginForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Сброс ошибок
|
||||
$('.form-control').removeClass('is-invalid');
|
||||
$('.invalid-feedback').hide();
|
||||
|
||||
const username = $('#username').val().trim();
|
||||
const password = $('#password').val().trim();
|
||||
let hasErrors = false;
|
||||
|
||||
// Валидация имени пользователя
|
||||
if (!username) {
|
||||
$('#username').addClass('is-invalid');
|
||||
$('#usernameError').text('Введите имя пользователя').show();
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Валидация пароля
|
||||
if (!password) {
|
||||
$('#password').addClass('is-invalid');
|
||||
$('#passwordError').text('Введите пароль').show();
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
$('#loginBtn').prop('disabled', true);
|
||||
$('#loginLoading').show();
|
||||
|
||||
// Вызываем метод входа из auth.js
|
||||
auth.login();
|
||||
});
|
||||
|
||||
// Убираем ошибки при вводе
|
||||
$('.form-control').on('input', function() {
|
||||
$(this).removeClass('is-invalid');
|
||||
$(this).siblings('.invalid-feedback').hide();
|
||||
});
|
||||
|
||||
// Автофокус на поле имени пользователя
|
||||
$('#username').focus();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
64
profile.html
64
profile.html
@ -9,51 +9,63 @@
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-warehouse me-2"></i>
|
||||
Склад-Менеджер
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="history.html">
|
||||
<i class="fas fa-history me-1"></i>История
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="search.html">
|
||||
<i class="fas fa-search me-1"></i>Поиск
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown">
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
@ -68,6 +80,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Заголовок профиля -->
|
||||
<div class="profile-header">
|
||||
<div class="container">
|
||||
@ -397,4 +427,4 @@
|
||||
<script src="js/warehouse.js"></script>
|
||||
<script src="js/profile.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
78
search.html
78
search.html
@ -9,51 +9,63 @@
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
|
||||
|
||||
<!-- Главная навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-warehouse me-2"></i>
|
||||
Склад-Менеджер
|
||||
<i class="fas fa-warehouse me-2 text-primary"></i>
|
||||
<span class="fw-bold">Склад-Менеджер</span>
|
||||
</a>
|
||||
|
||||
<!-- Поиск в шапке -->
|
||||
<div class="navbar-search me-auto ms-4">
|
||||
<div class="input-group" style="width: 400px;">
|
||||
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="catalog.html">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-boxes me-1"></i>Каталог
|
||||
</a>
|
||||
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
|
||||
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="history.html">
|
||||
<i class="fas fa-history me-1"></i>История
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="search.html">
|
||||
<i class="fas fa-search me-1"></i>Поиск
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-cog me-1"></i>Администрирование
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
|
||||
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
|
||||
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="profileDropdown">
|
||||
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-user me-1"></i>Профиль
|
||||
</a>
|
||||
@ -68,6 +80,24 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Панель категорий -->
|
||||
<div class="categories-bar bg-light border-bottom">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills categories-nav" id="categoriesNav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">
|
||||
<i class="fas fa-home me-1"></i>Главная
|
||||
</a>
|
||||
</li>
|
||||
<!-- Категории будут загружены динамически -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
<!-- Заголовок страницы -->
|
||||
@ -176,7 +206,7 @@
|
||||
<ul class="nav nav-tabs mb-3" id="resultsTabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="items-tab" data-bs-toggle="tab" href="#itemsResults">
|
||||
<i class="fas fa-boxes me-1"></i>Товары <span class="badge bg-secondary" id="itemsCount">0</span>
|
||||
<i class="fas fa-boxes me-1"></i>Позиции <span class="badge bg-secondary" id="itemsCount">0</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@ -232,8 +262,8 @@
|
||||
<!-- Пустое состояние -->
|
||||
<div id="emptyState" class="text-center py-5">
|
||||
<i class="fas fa-search fa-3x text-muted mb-3"></i>
|
||||
<h4>Начните поиск</h4>
|
||||
<p class="text-muted">Введите запрос в поле поиска выше, чтобы найти товары или операции</p>
|
||||
<h4>Начните поиск</h4>
|
||||
<p class="text-muted">Введите запрос в поле поиска выше, чтобы найти позиции или операции</p>
|
||||
</div>
|
||||
|
||||
<!-- Состояние загрузки -->
|
||||
@ -248,10 +278,10 @@
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-box me-2"></i>
|
||||
Детали товара
|
||||
</h5>
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-box me-2"></i>
|
||||
Детали позиции
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="itemDetailsContent">
|
||||
@ -289,4 +319,4 @@
|
||||
<script src="js/warehouse.js"></script>
|
||||
<script src="js/search.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user