Add files via upload
v1.3.0
This commit is contained in:
189
README.md
189
README.md
@ -12,6 +12,8 @@
|
||||
- **Поиск**: быстрый поиск позиций и операций
|
||||
- **Отчеты**: генерация различных отчетов
|
||||
- **Экспорт данных**: выгрузка в CSV и JSON форматах
|
||||
- **Настраиваемые категории**: полный CRUD для категорий позиций
|
||||
- **Hover-навигация**: выпадающее меню категорий при наведении
|
||||
|
||||
### Система безопасности
|
||||
- **Многоуровневая авторизация** с разными ролями
|
||||
@ -21,6 +23,7 @@
|
||||
- **Проверка прав доступа** для каждой операции
|
||||
- **Безопасная обработка ошибок** с информативными уведомлениями
|
||||
- **URL валидация** параметров и фильтров
|
||||
- **Автоматическое скрытие элементов** в зависимости от ролей
|
||||
|
||||
### Уровни доступа
|
||||
|
||||
@ -29,21 +32,25 @@
|
||||
- Управление пользователями
|
||||
- Настройка системы
|
||||
- Просмотр всех отчетов
|
||||
- Удаление позиций и категорий
|
||||
|
||||
#### 👔 Менеджер (manager/manager123)
|
||||
- Чтение и запись данных
|
||||
- Управление товарами
|
||||
- Оформление операций
|
||||
- Просмотр отчетов
|
||||
- Создание и редактирование категорий
|
||||
|
||||
#### 👷 Оператор (operator/operator123)
|
||||
- Только чтение данных
|
||||
- Просмотр каталога и истории
|
||||
- Экспорт данных
|
||||
- Ограниченный доступ к функциям
|
||||
|
||||
#### 👁️ Наблюдатель (viewer/viewer123)
|
||||
- Ограниченный просмотр
|
||||
- Только чтение данных
|
||||
- Экспорт данных
|
||||
- Минимальный доступ
|
||||
|
||||
## 📁 Структура проекта
|
||||
@ -56,13 +63,15 @@ warehouse-system/
|
||||
├── history.html # История операций
|
||||
├── search.html # Поиск
|
||||
├── profile.html # Профиль пользователя
|
||||
├── admin/
|
||||
│ └── categories.html # Управление категориями
|
||||
├── css/
|
||||
│ └── style.css # Основные стили
|
||||
├── js/
|
||||
│ ├── auth.js # Система авторизации
|
||||
│ ├── auth.js # Система авторизации и прав доступа
|
||||
│ ├── main.js # Основная логика
|
||||
│ ├── warehouse.js # Управление складом
|
||||
│ ├── catalog.js # Каталог товаров (с проверкой прав доступа)
|
||||
│ ├── catalog.js # Каталог товаров
|
||||
│ ├── history.js # История операций
|
||||
│ ├── search.js # Поиск
|
||||
│ ├── profile.js # Профиль пользователя
|
||||
@ -82,7 +91,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`
|
||||
@ -90,35 +114,38 @@ warehouse-system/
|
||||
- **Оператор**: `operator` / `operator123`
|
||||
- **Наблюдатель**: `viewer` / `viewer123`
|
||||
|
||||
### 2. Начальная настройка
|
||||
### 3. Начальная настройка
|
||||
1. Войдите как администратор
|
||||
2. Добавьте первые товары через главную страницу
|
||||
3. Настройте категории товаров
|
||||
4. Создайте пользователей с нужными правами
|
||||
2. Создайте категории позиций через "Администрирование" → "Категории позиций"
|
||||
3. Добавьте первые позиции через "Каталог" → "Добавить позицию"
|
||||
4. Настройте права доступа для пользователей
|
||||
|
||||
## 📖 Руководство пользователя
|
||||
|
||||
### Главная страница
|
||||
- **Быстрые действия**: добавление товаров, приход/расход
|
||||
- **Быстрые действия**: добавление товаров, приход/расход (согласно правам доступа)
|
||||
- **Статистика**: общая информация о складе
|
||||
- **Последние операции**: мониторинг активности
|
||||
- **Быстрая навигация**: выпадающее меню категорий
|
||||
- **Быстрая навигация**: выпадающее меню категорий при наведении
|
||||
|
||||
### Навигация по категориям
|
||||
### Hover-навигация по категориям
|
||||
- **Выпадающее меню**: появляется при наведении на "Каталог"
|
||||
- **Плавная анимация**: плавное появление и исчезновение
|
||||
- **Быстрый переход**: клик по категории открывает отфильтрованный каталог
|
||||
- **URL фильтрация**: прямые ссылки вида `catalog.html?category=electronics`
|
||||
- **Визуальная обратная связь**: иконки и цвета категорий
|
||||
- **Визуальная обратная связь**: иконки, цвета и количество позиций в категориях
|
||||
- **Единообразие**: одинаковое меню на всех страницах
|
||||
- **Умное поведение**: меню не закрывается при наведении на само меню
|
||||
|
||||
### Каталог позиций
|
||||
- **Просмотр**: все позиции с фильтрами и статистикой
|
||||
- **Поиск**: по названию, описанию, штрих-коду
|
||||
- **Фильтры**: по категории, цене, наличию, статусу запасов
|
||||
- **Редактирование**: изменение информации о позициях (только для админов и менеджеров)
|
||||
- **Удаление**: удаление позиций (только для администраторов)
|
||||
- **Экспорт**: выгрузка каталога в CSV формате
|
||||
- **Безопасность**: проверка прав доступа для всех операций
|
||||
- **Быстрая навигация**: выпадающее меню категорий при наведении
|
||||
- **Hover-навигация**: выпадающее меню категорий при наведении
|
||||
- **URL фильтрация**: прямая ссылка на отфильтрованный каталог
|
||||
|
||||
### История операций
|
||||
@ -132,7 +159,7 @@ warehouse-system/
|
||||
- **Расширенные фильтры**: точная настройка
|
||||
- **Результаты**: с подсветкой найденного
|
||||
- **Экспорт результатов**: сохранение найденного
|
||||
- **Быстрая навигация**: переход к категориям из результатов поиска
|
||||
- **Hover-навигация**: переход к категориям из результатов поиска
|
||||
|
||||
### Профиль пользователя
|
||||
- **Информация**: личные данные и права
|
||||
@ -140,6 +167,12 @@ warehouse-system/
|
||||
- **Настройки**: персонализация интерфейса
|
||||
- **Безопасность**: смена пароля
|
||||
|
||||
### Администрирование
|
||||
- **Управление категориями**: создание, редактирование, удаление категорий
|
||||
- **Настройка иконок**: выбор иконок Font Awesome для категорий
|
||||
- **Цветовая схема**: настройка цветов категорий
|
||||
- **Активность категорий**: включение/отключение категорий
|
||||
|
||||
## 🔧 Администрирование
|
||||
|
||||
### Управление пользователями
|
||||
@ -149,10 +182,10 @@ warehouse-system/
|
||||
4. Создавайте, редактируйте, удаляйте пользователей
|
||||
|
||||
### Настройка категорий
|
||||
1. Откройте "Категории позиций"
|
||||
2. Добавьте нужные категории
|
||||
3. Настройте иконки и описания
|
||||
4. Категории автоматически появятся в выпадающем меню
|
||||
1. Откройте "Администрирование" → "Категории позиций"
|
||||
2. Добавьте нужные категории с иконками и цветами
|
||||
3. Настройте описания и активность
|
||||
4. Категории автоматически появятся в hover-меню
|
||||
5. Изменения применяются мгновенно на всех страницах
|
||||
|
||||
### Генерация отчетов
|
||||
@ -176,8 +209,8 @@ warehouse-system/
|
||||
minQuantity: 10,
|
||||
location: "Стеллаж A-1",
|
||||
supplier: "Поставщик",
|
||||
createdAt: "2025-01-15T10:00:00Z",
|
||||
updatedAt: "2025-01-15T10:00:00Z"
|
||||
createdAt: "2025-08-15T10:00:00Z",
|
||||
updatedAt: "2025-08-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@ -191,7 +224,7 @@ warehouse-system/
|
||||
quantity: 10,
|
||||
employeeId: 1,
|
||||
employeeName: "Имя сотрудника",
|
||||
date: "2025-01-15T10:00:00Z",
|
||||
date: "2025-08-15T10:00:00Z",
|
||||
status: "completed",
|
||||
supplier: "Поставщик",
|
||||
recipient: "Получатель",
|
||||
@ -223,8 +256,9 @@ warehouse-system/
|
||||
color: "#2c5aa0",
|
||||
active: true,
|
||||
itemCount: 25,
|
||||
createdAt: "2025-01-15T10:00:00Z",
|
||||
updatedAt: "2025-01-15T10:00:00Z"
|
||||
totalQuantity: 150,
|
||||
createdAt: "2025-08-15T10:00:00Z",
|
||||
updatedAt: "2025-08-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@ -243,34 +277,48 @@ warehouse-system/
|
||||
}
|
||||
```
|
||||
|
||||
### Добавление новых категорий
|
||||
В файле `js/warehouse.js` найдите функцию `loadCategories()` и добавьте новые категории:
|
||||
```javascript
|
||||
{
|
||||
id: 'new-category',
|
||||
name: 'Новая категория',
|
||||
icon: 'fas fa-icon-name',
|
||||
color: '#2c5aa0',
|
||||
description: 'Описание категории',
|
||||
active: true
|
||||
}
|
||||
```
|
||||
|
||||
### Настройка выпадающего меню
|
||||
Категории автоматически появляются в выпадающем меню навигации. Для настройки стилей отредактируйте CSS в `css/style.css`:
|
||||
### Настройка hover-меню категорий
|
||||
Для настройки стилей hover-меню отредактируйте CSS в `css/style.css`:
|
||||
```css
|
||||
#catalogCategoriesDropdown {
|
||||
min-width: 250px;
|
||||
max-height: 400px;
|
||||
min-width: 280px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Hover dropdown functionality */
|
||||
.nav-item.dropdown:hover .dropdown-menu {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.nav-item.dropdown:hover .dropdown-toggle::after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
```
|
||||
|
||||
### Настройка прав доступа
|
||||
В файле `js/auth.js` измените массив пользователей или добавьте новые роли и разрешения.
|
||||
В файле `js/auth.js` измените массив пользователей или добавьте новые роли и разрешения. Система автоматически скрывает элементы в зависимости от ролей пользователя.
|
||||
|
||||
### Добавление новых категорий
|
||||
Через интерфейс системы:
|
||||
1. Войдите как администратор или менеджер
|
||||
2. Перейдите в "Администрирование" → "Категории позиций"
|
||||
3. Нажмите "Добавить категорию"
|
||||
4. Заполните форму с иконкой, цветом и описанием
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
### Система управления правами доступа
|
||||
- **Автоматическое скрытие элементов**: кнопки и функции скрываются согласно ролям
|
||||
- **Проверка прав на сервере**: все операции проверяют права доступа
|
||||
- **Безопасные сессии**: данные пользователя хранятся в LocalStorage
|
||||
- **Валидация данных**: проверка всех входных данных
|
||||
- **Информативные подсказки**: кнопки показывают требуемые права
|
||||
|
||||
### Рекомендации по безопасности
|
||||
1. **Измените пароли по умолчанию** при первом входе
|
||||
2. **Используйте HTTPS** в продакшене
|
||||
@ -278,6 +326,13 @@ warehouse-system/
|
||||
4. **Ведите логи** всех операций
|
||||
5. **Делайте резервные копии** данных
|
||||
|
||||
### Настройка HTTPS
|
||||
Для продакшена обязательно используйте SSL-сертификат:
|
||||
```bash
|
||||
# Пример с Let's Encrypt
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
## 📈 Производительность
|
||||
|
||||
### Оптимизация
|
||||
@ -286,6 +341,13 @@ warehouse-system/
|
||||
- Минимизируйте количество одновременных операций
|
||||
- Регулярно очищайте старые данные
|
||||
|
||||
### Масштабирование
|
||||
- Для больших объемов данных используйте базу данных
|
||||
- Добавьте серверную часть (PHP, Node.js, Python)
|
||||
- Используйте CDN для статических файлов
|
||||
- Настройте кэширование на сервере
|
||||
- Оптимизируйте загрузку категорий для больших каталогов
|
||||
|
||||
### Технические улучшения
|
||||
- **Безопасная обработка данных**: проверка на null/undefined
|
||||
- **Валидация форм**: клиентская проверка ввода
|
||||
@ -295,10 +357,21 @@ warehouse-system/
|
||||
- **URL маршрутизация**: обработка параметров и фильтров
|
||||
- **Асинхронная загрузка**: динамическое обновление меню
|
||||
- **Кэширование данных**: оптимизация производительности
|
||||
- **Hover-функциональность**: плавные анимации и переходы
|
||||
|
||||
## 🔄 Последние обновления
|
||||
|
||||
### Версия 1.2.0 (Август 2025)
|
||||
### Версия 1.3.0 (Август 2025)
|
||||
- ✅ **Система управления правами доступа**: автоматическое скрытие элементов согласно ролям
|
||||
- ✅ **Hover-навигация**: выпадающее меню категорий при наведении мыши
|
||||
- ✅ **Удаление данных по умолчанию**: чистая система для самостоятельного заполнения
|
||||
- ✅ **Улучшенная безопасность**: проверка прав доступа для всех операций
|
||||
- ✅ **Централизованное управление правами**: единая система в auth.js
|
||||
- ✅ **Информативные подсказки**: кнопки показывают требуемые права
|
||||
- ✅ **Плавные анимации**: hover-эффекты и переходы
|
||||
- ✅ **Умное поведение меню**: предотвращение закрытия при наведении
|
||||
|
||||
### Версия 1.2.0 (Январь 2025)
|
||||
- ✅ **Добавлено выпадающее меню категорий**: быстрая навигация по категориям
|
||||
- ✅ **Реализована URL фильтрация**: прямые ссылки на отфильтрованный каталог
|
||||
- ✅ **Улучшена навигация**: единообразное меню на всех страницах
|
||||
@ -306,7 +379,7 @@ warehouse-system/
|
||||
- ✅ **Улучшен UX**: hover эффекты и визуальная обратная связь
|
||||
- ✅ **Расширена функциональность**: переходы между категориями из любого места
|
||||
|
||||
### Версия 1.1.0 (Август 2025)
|
||||
### Версия 1.1.0 (Январь 2025)
|
||||
- ✅ **Улучшена безопасность каталога**: добавлена проверка прав доступа
|
||||
- ✅ **Исправлена навигация**: добавлена ссылка "Поиск" в главное меню
|
||||
- ✅ **Улучшена обработка ошибок**: безопасная работа с пустыми данными
|
||||
@ -341,7 +414,19 @@ warehouse-system/
|
||||
- Проверьте консоль на ошибки загрузки данных
|
||||
- Очистите фильтры, если каталог пустой
|
||||
- Проверьте URL параметры для фильтрации по категориям
|
||||
- Убедитесь, что выпадающее меню категорий загружается
|
||||
- Убедитесь, что hover-меню категорий загружается
|
||||
|
||||
#### Не работает hover-меню
|
||||
- Проверьте, что JavaScript загружен корректно
|
||||
- Убедитесь, что jQuery подключен
|
||||
- Проверьте консоль на ошибки
|
||||
- Очистите кэш браузера
|
||||
|
||||
#### Элементы интерфейса скрыты
|
||||
- Проверьте роль пользователя
|
||||
- Убедитесь, что пользователь авторизован
|
||||
- Проверьте права доступа в auth.js
|
||||
- Очистите LocalStorage и войдите заново
|
||||
|
||||
### Логи и отладка
|
||||
Откройте консоль разработчика (F12) для просмотра ошибок и логов.
|
||||
@ -363,20 +448,22 @@ warehouse-system/
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Этот проект распространяется под лицензией MIT.
|
||||
Этот проект распространяется под лицензией MIT. См. файл LICENSE для подробностей.
|
||||
|
||||
## 👥 Авторы
|
||||
|
||||
- Разработка: Roman Pylaev
|
||||
- Дизайн: Roman Pylaev
|
||||
- Тестирование: Roman Pylaev
|
||||
- Разработка: [Ваше имя]
|
||||
- Дизайн: [Имя дизайнера]
|
||||
- Тестирование: [Имя тестировщика]
|
||||
|
||||
## 📞 Контакты
|
||||
|
||||
- Telegram/Discord: @dark7es
|
||||
- Email: support@warehouse-system.com
|
||||
- Телефон: +7 (XXX) XXX-XX-XX
|
||||
- Веб-сайт: https://warehouse-system.com
|
||||
|
||||
---
|
||||
|
||||
**Версия**: 1.2.0
|
||||
**Дата**: Январь 2025
|
||||
**Последнее обновление**: 15.01.2025
|
||||
**Версия**: 1.3.0
|
||||
**Дата**: Август 2025
|
||||
**Последнее обновление**: 15.08.2025
|
||||
@ -107,7 +107,7 @@
|
||||
<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">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal" id="addCategoryBtn" data-role="manager">
|
||||
<i class="fas fa-plus me-1"></i>Добавить категорию
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -117,11 +117,11 @@
|
||||
<p class="text-muted mb-0">Управление позициями склада</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-outline-primary" id="exportBtn">
|
||||
<button class="btn btn-outline-primary" id="exportBtn" data-role="authenticated">
|
||||
<i class="fas fa-download me-1"></i>Экспорт
|
||||
</button>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn"
|
||||
style="display: none;" title="Требуются права администратора или менеджера">
|
||||
data-role="manager" style="display: none;" title="Требуются права администратора или менеджера">
|
||||
<i class="fas fa-plus me-1"></i>Добавить позицию
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -571,8 +571,8 @@ footer {
|
||||
|
||||
/* Стили для выпадающего меню категорий */
|
||||
#catalogCategoriesDropdown {
|
||||
min-width: 250px;
|
||||
max-height: 400px;
|
||||
min-width: 280px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
@ -587,6 +587,7 @@ footer {
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@ -596,6 +597,28 @@ footer {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item .category-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item .category-count {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
color: inherit;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
min-width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item:hover .category-count {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
@ -606,6 +629,16 @@ footer {
|
||||
border-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Стили для пустых категорий */
|
||||
#catalogCategoriesDropdown .dropdown-item.empty-category {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#catalogCategoriesDropdown .dropdown-item.empty-category .category-count {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Анимация появления выпадающего меню */
|
||||
.dropdown-menu.show {
|
||||
animation: dropdownFadeIn 0.2s ease-out;
|
||||
@ -620,4 +653,18 @@ footer {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover dropdown functionality */
|
||||
.nav-item.dropdown:hover .dropdown-menu {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.nav-item.dropdown:hover .dropdown-toggle::after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Smooth transition for dropdown arrow */
|
||||
.dropdown-toggle::after {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
14
index.html
14
index.html
@ -120,37 +120,37 @@
|
||||
<div class="col-12">
|
||||
<h2 class="mb-3">Быстрые действия</h2>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="col-md-3 mb-3" data-role="manager">
|
||||
<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>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn">
|
||||
Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="col-md-3 mb-3" data-role="manager">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<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>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#incomingModal">
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#incomingModal" id="incomingBtn">
|
||||
Оформить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="col-md-3 mb-3" data-role="manager">
|
||||
<div class="card h-100 quick-action-card">
|
||||
<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>
|
||||
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#outgoingModal">
|
||||
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#outgoingModal" id="outgoingBtn">
|
||||
Оформить
|
||||
</button>
|
||||
</div>
|
||||
@ -200,7 +200,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="col-md-3 mb-3" data-role="admin">
|
||||
<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>
|
||||
|
||||
100
js/auth.js
100
js/auth.js
@ -175,24 +175,108 @@ class AuthSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление прав доступа для кнопок
|
||||
// Обновление прав доступа для кнопок и элементов интерфейса
|
||||
updateButtonPermissions() {
|
||||
// Кнопки, доступные только администраторам
|
||||
const adminButtons = [
|
||||
// Элементы, доступные только администраторам
|
||||
const adminElements = [
|
||||
'#addItemBtn',
|
||||
'#editItemBtn',
|
||||
'#editItemBtn',
|
||||
'#deleteItemBtn',
|
||||
'#manageUsersBtn'
|
||||
'#manageUsersBtn',
|
||||
'.admin-only',
|
||||
'[data-role="admin"]'
|
||||
];
|
||||
|
||||
adminButtons.forEach(buttonId => {
|
||||
// Элементы, доступные администраторам и менеджерам
|
||||
const managerElements = [
|
||||
'.manager-or-admin',
|
||||
'[data-role="manager"]',
|
||||
'#addCategoryBtn',
|
||||
'#editCategoryBtn'
|
||||
];
|
||||
|
||||
// Элементы, доступные всем авторизованным пользователям
|
||||
const authenticatedElements = [
|
||||
'.authenticated-only',
|
||||
'[data-role="authenticated"]',
|
||||
'#exportBtn',
|
||||
'#searchBtn'
|
||||
];
|
||||
|
||||
// Скрываем/показываем элементы для администраторов
|
||||
adminElements.forEach(selector => {
|
||||
const elements = $(selector);
|
||||
elements.each((index, element) => {
|
||||
const $element = $(element);
|
||||
if (this.hasRole('admin')) {
|
||||
$element.show();
|
||||
$element.prop('disabled', false);
|
||||
} else {
|
||||
$element.hide();
|
||||
$element.prop('disabled', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Скрываем/показываем элементы для менеджеров и администраторов
|
||||
managerElements.forEach(selector => {
|
||||
const elements = $(selector);
|
||||
elements.each((index, element) => {
|
||||
const $element = $(element);
|
||||
if (this.hasRole('admin') || this.hasRole('manager')) {
|
||||
$element.show();
|
||||
$element.prop('disabled', false);
|
||||
} else {
|
||||
$element.hide();
|
||||
$element.prop('disabled', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Скрываем/показываем элементы для авторизованных пользователей
|
||||
authenticatedElements.forEach(selector => {
|
||||
const elements = $(selector);
|
||||
elements.each((index, element) => {
|
||||
const $element = $(element);
|
||||
if (this.isUserAuthenticated()) {
|
||||
$element.show();
|
||||
$element.prop('disabled', false);
|
||||
} else {
|
||||
$element.hide();
|
||||
$element.prop('disabled', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Специальная обработка для кнопок с подсказками
|
||||
this.updateButtonTooltips();
|
||||
}
|
||||
|
||||
// Обновление подсказок для кнопок
|
||||
updateButtonTooltips() {
|
||||
// Кнопки с подсказками о правах доступа
|
||||
const buttonPermissions = {
|
||||
'#addItemBtn': { roles: ['admin', 'manager'], message: 'Требуются права администратора или менеджера' },
|
||||
'#editItemBtn': { roles: ['admin', 'manager'], message: 'Требуются права администратора или менеджера' },
|
||||
'#deleteItemBtn': { roles: ['admin'], message: 'Требуются права администратора' },
|
||||
'#addCategoryBtn': { roles: ['admin', 'manager'], message: 'Требуются права администратора или менеджера' },
|
||||
'#editCategoryBtn': { roles: ['admin', 'manager'], message: 'Требуются права администратора или менеджера' },
|
||||
'#deleteCategoryBtn': { roles: ['admin'], message: 'Требуются права администратора' },
|
||||
'#exportBtn': { roles: ['admin', 'manager', 'operator', 'viewer'], message: 'Требуется авторизация' }
|
||||
};
|
||||
|
||||
Object.keys(buttonPermissions).forEach(buttonId => {
|
||||
const button = $(buttonId);
|
||||
if (button.length) {
|
||||
if (this.hasRole('admin')) {
|
||||
const permission = buttonPermissions[buttonId];
|
||||
const hasAccess = permission.roles.some(role => this.hasRole(role));
|
||||
|
||||
if (hasAccess) {
|
||||
button.prop('disabled', false);
|
||||
button.removeAttr('title');
|
||||
} else {
|
||||
button.prop('disabled', true);
|
||||
button.attr('title', 'Требуются права администратора');
|
||||
button.attr('title', permission.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -130,11 +130,6 @@ class CatalogManager {
|
||||
|
||||
// Заполнение фильтра категорий
|
||||
populateCategoryFilters(categories) {
|
||||
if (!categories || categories.length === 0) {
|
||||
console.warn('Нет категорий для отображения');
|
||||
return;
|
||||
}
|
||||
|
||||
const categoryFilter = $('#categoryFilter');
|
||||
const addItemCategory = $('#itemCategory');
|
||||
const editItemCategory = $('#editItemCategory');
|
||||
@ -144,15 +139,23 @@ class CatalogManager {
|
||||
addItemCategory.find('option:not(:first)').remove();
|
||||
editItemCategory.find('option:not(:first)').remove();
|
||||
|
||||
// Добавляем опцию "Без категории"
|
||||
const uncategorizedOption = `<option value="uncategorized">Без категории</option>`;
|
||||
categoryFilter.append(uncategorizedOption);
|
||||
addItemCategory.append(uncategorizedOption);
|
||||
editItemCategory.append(uncategorizedOption);
|
||||
|
||||
// Добавляем категории
|
||||
categories.forEach(category => {
|
||||
if (category && category.id && category.name) {
|
||||
const option = `<option value="${category.id}">${category.name}</option>`;
|
||||
categoryFilter.append(option);
|
||||
addItemCategory.append(option);
|
||||
editItemCategory.append(option);
|
||||
}
|
||||
});
|
||||
if (categories && categories.length > 0) {
|
||||
categories.forEach(category => {
|
||||
if (category && category.id && category.name) {
|
||||
const option = `<option value="${category.id}">${category.name}</option>`;
|
||||
categoryFilter.append(option);
|
||||
addItemCategory.append(option);
|
||||
editItemCategory.append(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка поиска
|
||||
@ -441,8 +444,8 @@ class CatalogManager {
|
||||
|
||||
// Получение информации о категории
|
||||
getCategoryInfo(categoryId) {
|
||||
if (!categoryId) {
|
||||
return { id: 'unknown', name: 'Без категории', icon: 'fas fa-box' };
|
||||
if (!categoryId || categoryId === 'uncategorized') {
|
||||
return { id: 'uncategorized', name: 'Без категории', icon: 'fas fa-box' };
|
||||
}
|
||||
|
||||
// Используем категории из warehouse
|
||||
@ -723,6 +726,9 @@ class CatalogManager {
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
@ -755,18 +761,8 @@ class CatalogManager {
|
||||
|
||||
// Обновление прав доступа
|
||||
updatePermissions() {
|
||||
const user = auth.getCurrentUser();
|
||||
if (!user) return;
|
||||
|
||||
// Показываем кнопку добавления для администраторов и менеджеров
|
||||
if (auth.hasRole('admin') || auth.hasRole('manager')) {
|
||||
$('#addItemBtn').show();
|
||||
} else {
|
||||
$('#addItemBtn').hide();
|
||||
}
|
||||
|
||||
// Показываем кнопку экспорта для всех авторизованных пользователей
|
||||
$('#exportBtn').show();
|
||||
// Используем централизованную систему управления правами из auth.js
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
@ -781,21 +777,60 @@ class CatalogManager {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
|
||||
// Обработка параметра категории из URL
|
||||
handleCategoryFromUrl() {
|
||||
|
||||
@ -350,13 +350,22 @@ class CategoryManager {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в выпадающее меню
|
||||
@ -371,21 +380,60 @@ class CategoryManager {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="../catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация при загрузке страницы
|
||||
|
||||
@ -658,13 +658,22 @@ class HistoryManager {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
@ -707,21 +716,60 @@ class HistoryManager {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера истории
|
||||
|
||||
48
js/main.js
48
js/main.js
@ -494,6 +494,9 @@ class MainApp {
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
@ -536,21 +539,60 @@ class MainApp {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация приложения
|
||||
|
||||
@ -469,13 +469,22 @@ class ProfileManager {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
@ -518,21 +527,60 @@ class ProfileManager {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера профиля
|
||||
|
||||
56
js/search.js
56
js/search.js
@ -705,13 +705,22 @@ class SearchManager {
|
||||
$('#currentUserInfo').text(user.name);
|
||||
$('#loginLink').hide();
|
||||
$('#profileDropdown').show();
|
||||
$('#adminDropdown').show();
|
||||
|
||||
// Показываем админ панель только для администраторов
|
||||
if (auth.hasRole('admin')) {
|
||||
$('#adminDropdown').show();
|
||||
} else {
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
} else {
|
||||
$('#currentUserInfo').text('Гость');
|
||||
$('#loginLink').show();
|
||||
$('#profileDropdown').hide();
|
||||
$('#adminDropdown').hide();
|
||||
}
|
||||
|
||||
// Обновляем права доступа для элементов интерфейса
|
||||
auth.updateButtonPermissions();
|
||||
}
|
||||
|
||||
// Загрузка категорий в навигацию
|
||||
@ -754,21 +763,60 @@ class SearchManager {
|
||||
// Добавляем активные категории
|
||||
categories.forEach(category => {
|
||||
if (category.active) {
|
||||
const isEmpty = category.itemCount === 0;
|
||||
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 class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
|
||||
<div class="category-info">
|
||||
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
|
||||
<span>${category.name}</span>
|
||||
</div>
|
||||
<span class="category-count">${category.itemCount || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
dropdown.append(categoryLink);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализируем hover-функциональность
|
||||
this.initHoverDropdown();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация hover-функциональности для выпадающего меню
|
||||
initHoverDropdown() {
|
||||
const $dropdown = $('.nav-item.dropdown');
|
||||
const $dropdownMenu = $dropdown.find('.dropdown-menu');
|
||||
let hoverTimeout;
|
||||
|
||||
// Показываем меню при наведении
|
||||
$dropdown.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
$(this).find('.dropdown-menu').addClass('show');
|
||||
});
|
||||
|
||||
// Скрываем меню при уходе мыши
|
||||
$dropdown.on('mouseleave', function() {
|
||||
const $menu = $(this).find('.dropdown-menu');
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$menu.removeClass('show');
|
||||
}, 150); // Небольшая задержка для плавности
|
||||
});
|
||||
|
||||
// Предотвращаем скрытие при наведении на само меню
|
||||
$dropdownMenu.on('mouseenter', function() {
|
||||
clearTimeout(hoverTimeout);
|
||||
});
|
||||
|
||||
$dropdownMenu.on('mouseleave', function() {
|
||||
hoverTimeout = setTimeout(() => {
|
||||
$(this).removeClass('show');
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация менеджера поиска
|
||||
|
||||
153
js/warehouse.js
153
js/warehouse.js
@ -36,58 +36,7 @@ class WarehouseManager {
|
||||
|
||||
// Получение категорий по умолчанию
|
||||
getDefaultCategories() {
|
||||
return [
|
||||
{
|
||||
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()
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
// Сохранение категорий
|
||||
@ -98,7 +47,9 @@ class WarehouseManager {
|
||||
// Обновление количества позиций в категориях
|
||||
updateCategoryItemCounts() {
|
||||
this.categories.forEach(category => {
|
||||
category.itemCount = this.items.filter(item => item.category === category.id).length;
|
||||
const itemsInCategory = this.items.filter(item => item.category === category.id);
|
||||
category.itemCount = itemsInCategory.length;
|
||||
category.totalQuantity = itemsInCategory.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
||||
});
|
||||
this.saveCategories();
|
||||
}
|
||||
@ -114,78 +65,7 @@ class WarehouseManager {
|
||||
|
||||
// Получение позиций по умолчанию
|
||||
getDefaultItems() {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Ноутбук Dell Inspiron',
|
||||
category: 'electronics',
|
||||
price: 45000,
|
||||
quantity: 15,
|
||||
description: '15.6" Full HD, Intel Core i5, 8GB RAM, 256GB SSD',
|
||||
barcode: '1234567890123',
|
||||
minQuantity: 5,
|
||||
location: 'Стеллаж A-1',
|
||||
supplier: 'Dell Inc.',
|
||||
createdAt: '2025-01-15T10:00:00Z',
|
||||
updatedAt: '2025-01-15T10:00:00Z'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Принтер HP LaserJet',
|
||||
category: 'electronics',
|
||||
price: 12000,
|
||||
quantity: 8,
|
||||
description: 'Лазерный принтер, монохромный, 20 стр/мин',
|
||||
barcode: '1234567890124',
|
||||
minQuantity: 3,
|
||||
location: 'Стеллаж A-2',
|
||||
supplier: 'HP Inc.',
|
||||
createdAt: '2025-01-10T14:30:00Z',
|
||||
updatedAt: '2025-01-10T14:30:00Z'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Офисный стул',
|
||||
category: 'office',
|
||||
price: 3500,
|
||||
quantity: 25,
|
||||
description: 'Офисный стул с подлокотниками, черный',
|
||||
barcode: '1234567890125',
|
||||
minQuantity: 10,
|
||||
location: 'Стеллаж B-1',
|
||||
supplier: 'МебельСтрой',
|
||||
createdAt: '2025-01-05T09:15:00Z',
|
||||
updatedAt: '2025-01-05T09:15:00Z'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Бумага А4',
|
||||
category: 'office',
|
||||
price: 250,
|
||||
quantity: 100,
|
||||
description: 'Бумага А4, 80 г/м², 500 листов',
|
||||
barcode: '1234567890126',
|
||||
minQuantity: 20,
|
||||
location: 'Стеллаж C-1',
|
||||
supplier: 'ОфисМаркет',
|
||||
createdAt: '2025-01-12T11:45:00Z',
|
||||
updatedAt: '2025-01-12T11:45:00Z'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Отвертка крестовая',
|
||||
category: 'tools',
|
||||
price: 150,
|
||||
quantity: 50,
|
||||
description: 'Отвертка крестовая PH2, 150мм',
|
||||
barcode: '1234567890127',
|
||||
minQuantity: 15,
|
||||
location: 'Стеллаж D-1',
|
||||
supplier: 'ИнструментСтрой',
|
||||
createdAt: '2025-01-08T16:20:00Z',
|
||||
updatedAt: '2025-01-08T16:20:00Z'
|
||||
}
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
// Получение статистики
|
||||
@ -229,14 +109,14 @@ class WarehouseManager {
|
||||
const newItem = {
|
||||
id: this.getNextItemId(),
|
||||
name: itemData.itemName,
|
||||
category: itemData.itemCategory,
|
||||
category: itemData.itemCategory || 'uncategorized',
|
||||
price: parseFloat(itemData.itemPrice),
|
||||
quantity: parseInt(itemData.itemQuantity),
|
||||
description: itemData.itemDescription || '',
|
||||
barcode: itemData.itemBarcode || '',
|
||||
minQuantity: parseInt(itemData.itemQuantity) * 0.2, // 20% от начального количества
|
||||
location: 'Новый стеллаж',
|
||||
supplier: '',
|
||||
location: itemData.itemLocation || 'Новый стеллаж',
|
||||
supplier: itemData.itemSupplier || '',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
@ -244,6 +124,9 @@ class WarehouseManager {
|
||||
this.items.push(newItem);
|
||||
this.saveItems();
|
||||
|
||||
// Обновляем количество товаров в категории
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
// Создаем операцию прихода для новой позиции
|
||||
await this.createOperation({
|
||||
type: 'incoming',
|
||||
@ -269,6 +152,10 @@ class WarehouseManager {
|
||||
};
|
||||
|
||||
this.saveItems();
|
||||
|
||||
// Обновляем количество товаров в категории
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
return this.items[itemIndex];
|
||||
}
|
||||
|
||||
@ -281,6 +168,10 @@ class WarehouseManager {
|
||||
|
||||
this.items.splice(itemIndex, 1);
|
||||
this.saveItems();
|
||||
|
||||
// Обновляем количество товаров в категории
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -298,6 +189,9 @@ class WarehouseManager {
|
||||
item.quantity += quantity;
|
||||
item.updatedAt = new Date().toISOString();
|
||||
this.saveItems();
|
||||
|
||||
// Обновляем количество товаров в категории
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
// Создаем операцию
|
||||
const operation = await this.createOperation({
|
||||
@ -330,6 +224,9 @@ class WarehouseManager {
|
||||
item.quantity -= quantity;
|
||||
item.updatedAt = new Date().toISOString();
|
||||
this.saveItems();
|
||||
|
||||
// Обновляем количество товаров в категории
|
||||
this.updateCategoryItemCounts();
|
||||
|
||||
// Создаем операцию
|
||||
const operation = await this.createOperation({
|
||||
|
||||
Reference in New Issue
Block a user