Add files via upload

v1.3.0
This commit is contained in:
cyrteL
2025-07-28 18:24:57 +03:00
committed by GitHub
parent bd7afd54b5
commit e572c34e99
13 changed files with 632 additions and 248 deletions

189
README.md
View File

@ -12,6 +12,8 @@
- **Поиск**: быстрый поиск позиций и операций - **Поиск**: быстрый поиск позиций и операций
- **Отчеты**: генерация различных отчетов - **Отчеты**: генерация различных отчетов
- **Экспорт данных**: выгрузка в CSV и JSON форматах - **Экспорт данных**: выгрузка в CSV и JSON форматах
- **Настраиваемые категории**: полный CRUD для категорий позиций
- **Hover-навигация**: выпадающее меню категорий при наведении
### Система безопасности ### Система безопасности
- **Многоуровневая авторизация** с разными ролями - **Многоуровневая авторизация** с разными ролями
@ -21,6 +23,7 @@
- **Проверка прав доступа** для каждой операции - **Проверка прав доступа** для каждой операции
- **Безопасная обработка ошибок** с информативными уведомлениями - **Безопасная обработка ошибок** с информативными уведомлениями
- **URL валидация** параметров и фильтров - **URL валидация** параметров и фильтров
- **Автоматическое скрытие элементов** в зависимости от ролей
### Уровни доступа ### Уровни доступа
@ -29,21 +32,25 @@
- Управление пользователями - Управление пользователями
- Настройка системы - Настройка системы
- Просмотр всех отчетов - Просмотр всех отчетов
- Удаление позиций и категорий
#### 👔 Менеджер (manager/manager123) #### 👔 Менеджер (manager/manager123)
- Чтение и запись данных - Чтение и запись данных
- Управление товарами - Управление товарами
- Оформление операций - Оформление операций
- Просмотр отчетов - Просмотр отчетов
- Создание и редактирование категорий
#### 👷 Оператор (operator/operator123) #### 👷 Оператор (operator/operator123)
- Только чтение данных - Только чтение данных
- Просмотр каталога и истории - Просмотр каталога и истории
- Экспорт данных
- Ограниченный доступ к функциям - Ограниченный доступ к функциям
#### 👁️ Наблюдатель (viewer/viewer123) #### 👁️ Наблюдатель (viewer/viewer123)
- Ограниченный просмотр - Ограниченный просмотр
- Только чтение данных - Только чтение данных
- Экспорт данных
- Минимальный доступ - Минимальный доступ
## 📁 Структура проекта ## 📁 Структура проекта
@ -56,13 +63,15 @@ warehouse-system/
├── history.html # История операций ├── history.html # История операций
├── search.html # Поиск ├── search.html # Поиск
├── profile.html # Профиль пользователя ├── profile.html # Профиль пользователя
├── admin/
│ └── categories.html # Управление категориями
├── css/ ├── css/
│ └── style.css # Основные стили │ └── style.css # Основные стили
├── js/ ├── js/
│ ├── auth.js # Система авторизации │ ├── auth.js # Система авторизации и прав доступа
│ ├── main.js # Основная логика │ ├── main.js # Основная логика
│ ├── warehouse.js # Управление складом │ ├── warehouse.js # Управление складом
│ ├── catalog.js # Каталог товаров (с проверкой прав доступа) │ ├── catalog.js # Каталог товаров
│ ├── history.js # История операций │ ├── history.js # История операций
│ ├── search.js # Поиск │ ├── search.js # Поиск
│ ├── profile.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` 1. Откройте `login.html`
2. Используйте тестовые учетные записи: 2. Используйте тестовые учетные записи:
- **Администратор**: `admin` / `admin123` - **Администратор**: `admin` / `admin123`
@ -90,35 +114,38 @@ warehouse-system/
- **Оператор**: `operator` / `operator123` - **Оператор**: `operator` / `operator123`
- **Наблюдатель**: `viewer` / `viewer123` - **Наблюдатель**: `viewer` / `viewer123`
### 2. Начальная настройка ### 3. Начальная настройка
1. Войдите как администратор 1. Войдите как администратор
2. Добавьте первые товары через главную страницу 2. Создайте категории позиций через "Администрирование" → "Категории позиций"
3. Настройте категории товаров 3. Добавьте первые позиции через "Каталог" → "Добавить позицию"
4. Создайте пользователей с нужными правами 4. Настройте права доступа для пользователей
## 📖 Руководство пользователя ## 📖 Руководство пользователя
### Главная страница ### Главная страница
- **Быстрые действия**: добавление товаров, приход/расход - **Быстрые действия**: добавление товаров, приход/расход (согласно правам доступа)
- **Статистика**: общая информация о складе - **Статистика**: общая информация о складе
- **Последние операции**: мониторинг активности - **Последние операции**: мониторинг активности
- **Быстрая навигация**: выпадающее меню категорий - **Быстрая навигация**: выпадающее меню категорий при наведении
### Навигация по категориям ### Hover-навигация по категориям
- **Выпадающее меню**: появляется при наведении на "Каталог" - **Выпадающее меню**: появляется при наведении на "Каталог"
- **Плавная анимация**: плавное появление и исчезновение
- **Быстрый переход**: клик по категории открывает отфильтрованный каталог - **Быстрый переход**: клик по категории открывает отфильтрованный каталог
- **URL фильтрация**: прямые ссылки вида `catalog.html?category=electronics` - **URL фильтрация**: прямые ссылки вида `catalog.html?category=electronics`
- **Визуальная обратная связь**: иконки и цвета категорий - **Визуальная обратная связь**: иконки, цвета и количество позиций в категориях
- **Единообразие**: одинаковое меню на всех страницах - **Единообразие**: одинаковое меню на всех страницах
- **Умное поведение**: меню не закрывается при наведении на само меню
### Каталог позиций ### Каталог позиций
- **Просмотр**: все позиции с фильтрами и статистикой - **Просмотр**: все позиции с фильтрами и статистикой
- **Поиск**: по названию, описанию, штрих-коду - **Поиск**: по названию, описанию, штрих-коду
- **Фильтры**: по категории, цене, наличию, статусу запасов - **Фильтры**: по категории, цене, наличию, статусу запасов
- **Редактирование**: изменение информации о позициях (только для админов и менеджеров) - **Редактирование**: изменение информации о позициях (только для админов и менеджеров)
- **Удаление**: удаление позиций (только для администраторов)
- **Экспорт**: выгрузка каталога в CSV формате - **Экспорт**: выгрузка каталога в CSV формате
- **Безопасность**: проверка прав доступа для всех операций - **Безопасность**: проверка прав доступа для всех операций
- **Быстрая навигация**: выпадающее меню категорий при наведении - **Hover-навигация**: выпадающее меню категорий при наведении
- **URL фильтрация**: прямая ссылка на отфильтрованный каталог - **URL фильтрация**: прямая ссылка на отфильтрованный каталог
### История операций ### История операций
@ -132,7 +159,7 @@ warehouse-system/
- **Расширенные фильтры**: точная настройка - **Расширенные фильтры**: точная настройка
- **Результаты**: с подсветкой найденного - **Результаты**: с подсветкой найденного
- **Экспорт результатов**: сохранение найденного - **Экспорт результатов**: сохранение найденного
- **Быстрая навигация**: переход к категориям из результатов поиска - **Hover-навигация**: переход к категориям из результатов поиска
### Профиль пользователя ### Профиль пользователя
- **Информация**: личные данные и права - **Информация**: личные данные и права
@ -140,6 +167,12 @@ warehouse-system/
- **Настройки**: персонализация интерфейса - **Настройки**: персонализация интерфейса
- **Безопасность**: смена пароля - **Безопасность**: смена пароля
### Администрирование
- **Управление категориями**: создание, редактирование, удаление категорий
- **Настройка иконок**: выбор иконок Font Awesome для категорий
- **Цветовая схема**: настройка цветов категорий
- **Активность категорий**: включение/отключение категорий
## 🔧 Администрирование ## 🔧 Администрирование
### Управление пользователями ### Управление пользователями
@ -149,10 +182,10 @@ warehouse-system/
4. Создавайте, редактируйте, удаляйте пользователей 4. Создавайте, редактируйте, удаляйте пользователей
### Настройка категорий ### Настройка категорий
1. Откройте "Категории позиций" 1. Откройте "Администрирование" → "Категории позиций"
2. Добавьте нужные категории 2. Добавьте нужные категории с иконками и цветами
3. Настройте иконки и описания 3. Настройте описания и активность
4. Категории автоматически появятся в выпадающем меню 4. Категории автоматически появятся в hover-меню
5. Изменения применяются мгновенно на всех страницах 5. Изменения применяются мгновенно на всех страницах
### Генерация отчетов ### Генерация отчетов
@ -176,8 +209,8 @@ warehouse-system/
minQuantity: 10, minQuantity: 10,
location: "Стеллаж A-1", location: "Стеллаж A-1",
supplier: "Поставщик", supplier: "Поставщик",
createdAt: "2025-01-15T10:00:00Z", createdAt: "2025-08-15T10:00:00Z",
updatedAt: "2025-01-15T10:00:00Z" updatedAt: "2025-08-15T10:00:00Z"
} }
``` ```
@ -191,7 +224,7 @@ warehouse-system/
quantity: 10, quantity: 10,
employeeId: 1, employeeId: 1,
employeeName: "Имя сотрудника", employeeName: "Имя сотрудника",
date: "2025-01-15T10:00:00Z", date: "2025-08-15T10:00:00Z",
status: "completed", status: "completed",
supplier: "Поставщик", supplier: "Поставщик",
recipient: "Получатель", recipient: "Получатель",
@ -223,8 +256,9 @@ warehouse-system/
color: "#2c5aa0", color: "#2c5aa0",
active: true, active: true,
itemCount: 25, itemCount: 25,
createdAt: "2025-01-15T10:00:00Z", totalQuantity: 150,
updatedAt: "2025-01-15T10:00:00Z" createdAt: "2025-08-15T10:00:00Z",
updatedAt: "2025-08-15T10:00:00Z"
} }
``` ```
@ -243,34 +277,48 @@ warehouse-system/
} }
``` ```
### Добавление новых категорий ### Настройка hover-меню категорий
В файле `js/warehouse.js` найдите функцию `loadCategories()` и добавьте новые категории: Для настройки стилей hover-меню отредактируйте CSS в `css/style.css`:
```javascript
{
id: 'new-category',
name: 'Новая категория',
icon: 'fas fa-icon-name',
color: '#2c5aa0',
description: 'Описание категории',
active: true
}
```
### Настройка выпадающего меню
Категории автоматически появляются в выпадающем меню навигации. Для настройки стилей отредактируйте CSS в `css/style.css`:
```css ```css
#catalogCategoriesDropdown { #catalogCategoriesDropdown {
min-width: 250px; min-width: 280px;
max-height: 400px; max-height: 500px;
overflow-y: auto; 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. **Измените пароли по умолчанию** при первом входе 1. **Измените пароли по умолчанию** при первом входе
2. **Используйте HTTPS** в продакшене 2. **Используйте HTTPS** в продакшене
@ -278,6 +326,13 @@ warehouse-system/
4. **Ведите логи** всех операций 4. **Ведите логи** всех операций
5. **Делайте резервные копии** данных 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 - **Безопасная обработка данных**: проверка на null/undefined
- **Валидация форм**: клиентская проверка ввода - **Валидация форм**: клиентская проверка ввода
@ -295,10 +357,21 @@ warehouse-system/
- **URL маршрутизация**: обработка параметров и фильтров - **URL маршрутизация**: обработка параметров и фильтров
- **Асинхронная загрузка**: динамическое обновление меню - **Асинхронная загрузка**: динамическое обновление меню
- **Кэширование данных**: оптимизация производительности - **Кэширование данных**: оптимизация производительности
- **Hover-функциональность**: плавные анимации и переходы
## 🔄 Последние обновления ## 🔄 Последние обновления
### Версия 1.2.0 (Август 2025) ### Версия 1.3.0 (Август 2025)
-**Система управления правами доступа**: автоматическое скрытие элементов согласно ролям
-**Hover-навигация**: выпадающее меню категорий при наведении мыши
-**Удаление данных по умолчанию**: чистая система для самостоятельного заполнения
-**Улучшенная безопасность**: проверка прав доступа для всех операций
-**Централизованное управление правами**: единая система в auth.js
-**Информативные подсказки**: кнопки показывают требуемые права
-**Плавные анимации**: hover-эффекты и переходы
-**Умное поведение меню**: предотвращение закрытия при наведении
### Версия 1.2.0 (Январь 2025)
-**Добавлено выпадающее меню категорий**: быстрая навигация по категориям -**Добавлено выпадающее меню категорий**: быстрая навигация по категориям
-**Реализована URL фильтрация**: прямые ссылки на отфильтрованный каталог -**Реализована URL фильтрация**: прямые ссылки на отфильтрованный каталог
-**Улучшена навигация**: единообразное меню на всех страницах -**Улучшена навигация**: единообразное меню на всех страницах
@ -306,7 +379,7 @@ warehouse-system/
-**Улучшен UX**: hover эффекты и визуальная обратная связь -**Улучшен UX**: hover эффекты и визуальная обратная связь
-**Расширена функциональность**: переходы между категориями из любого места -**Расширена функциональность**: переходы между категориями из любого места
### Версия 1.1.0 (Август 2025) ### Версия 1.1.0 (Январь 2025)
-**Улучшена безопасность каталога**: добавлена проверка прав доступа -**Улучшена безопасность каталога**: добавлена проверка прав доступа
-**Исправлена навигация**: добавлена ссылка "Поиск" в главное меню -**Исправлена навигация**: добавлена ссылка "Поиск" в главное меню
-**Улучшена обработка ошибок**: безопасная работа с пустыми данными -**Улучшена обработка ошибок**: безопасная работа с пустыми данными
@ -341,7 +414,19 @@ warehouse-system/
- Проверьте консоль на ошибки загрузки данных - Проверьте консоль на ошибки загрузки данных
- Очистите фильтры, если каталог пустой - Очистите фильтры, если каталог пустой
- Проверьте URL параметры для фильтрации по категориям - Проверьте URL параметры для фильтрации по категориям
- Убедитесь, что выпадающее меню категорий загружается - Убедитесь, что hover-меню категорий загружается
#### Не работает hover-меню
- Проверьте, что JavaScript загружен корректно
- Убедитесь, что jQuery подключен
- Проверьте консоль на ошибки
- Очистите кэш браузера
#### Элементы интерфейса скрыты
- Проверьте роль пользователя
- Убедитесь, что пользователь авторизован
- Проверьте права доступа в auth.js
- Очистите LocalStorage и войдите заново
### Логи и отладка ### Логи и отладка
Откройте консоль разработчика (F12) для просмотра ошибок и логов. Откройте консоль разработчика (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 **Версия**: 1.3.0
**Дата**: Январь 2025 **Дата**: Август 2025
**Последнее обновление**: 15.01.2025 **Последнее обновление**: 15.08.2025

View File

@ -107,7 +107,7 @@
<p class="text-muted mb-0">Создание и редактирование категорий позиций</p> <p class="text-muted mb-0">Создание и редактирование категорий позиций</p>
</div> </div>
<div class="d-flex gap-2"> <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>Добавить категорию <i class="fas fa-plus me-1"></i>Добавить категорию
</button> </button>
</div> </div>

View File

@ -117,11 +117,11 @@
<p class="text-muted mb-0">Управление позициями склада</p> <p class="text-muted mb-0">Управление позициями склада</p>
</div> </div>
<div class="d-flex gap-2"> <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>Экспорт <i class="fas fa-download me-1"></i>Экспорт
</button> </button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn" <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>Добавить позицию <i class="fas fa-plus me-1"></i>Добавить позицию
</button> </button>
</div> </div>

View File

@ -571,8 +571,8 @@ footer {
/* Стили для выпадающего меню категорий */ /* Стили для выпадающего меню категорий */
#catalogCategoriesDropdown { #catalogCategoriesDropdown {
min-width: 250px; min-width: 280px;
max-height: 400px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
border: none; border: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
@ -587,6 +587,7 @@ footer {
transition: all 0.2s ease; transition: all 0.2s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
gap: 0.5rem; gap: 0.5rem;
} }
@ -596,6 +597,28 @@ footer {
transform: translateX(2px); 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 { #catalogCategoriesDropdown .dropdown-item i {
width: 16px; width: 16px;
text-align: center; text-align: center;
@ -606,6 +629,16 @@ footer {
border-color: #e9ecef; 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 { .dropdown-menu.show {
animation: dropdownFadeIn 0.2s ease-out; animation: dropdownFadeIn 0.2s ease-out;
@ -621,3 +654,17 @@ footer {
transform: translateY(0); 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;
}

View File

@ -120,37 +120,37 @@
<div class="col-12"> <div class="col-12">
<h2 class="mb-3">Быстрые действия</h2> <h2 class="mb-3">Быстрые действия</h2>
</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 h-100 quick-action-card">
<div class="card-body text-center"> <div class="card-body text-center">
<i class="fas fa-plus-circle fa-3x text-primary mb-3"></i> <i class="fas fa-plus-circle fa-3x text-primary mb-3"></i>
<h5 class="card-title">Добавить позицию</h5> <h5 class="card-title">Добавить позицию</h5>
<p class="card-text">Добавить новую позицию в каталог</p> <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> </button>
</div> </div>
</div> </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 h-100 quick-action-card">
<div class="card-body text-center"> <div class="card-body text-center">
<i class="fas fa-box-open fa-3x text-success mb-3"></i> <i class="fas fa-box-open fa-3x text-success mb-3"></i>
<h5 class="card-title">Приход</h5> <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 class="btn btn-success" data-bs-toggle="modal" data-bs-target="#incomingModal" id="incomingBtn">
Оформить Оформить
</button> </button>
</div> </div>
</div> </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 h-100 quick-action-card">
<div class="card-body text-center"> <div class="card-body text-center">
<i class="fas fa-truck fa-3x text-warning mb-3"></i> <i class="fas fa-truck fa-3x text-warning mb-3"></i>
<h5 class="card-title">Расход</h5> <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 class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#outgoingModal" id="outgoingBtn">
Оформить Оформить
</button> </button>
</div> </div>
@ -200,7 +200,7 @@
</div> </div>
</div> </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 h-100 quick-action-card">
<div class="card-body text-center"> <div class="card-body text-center">
<i class="fas fa-cog fa-3x text-warning mb-3"></i> <i class="fas fa-cog fa-3x text-warning mb-3"></i>

View File

@ -175,24 +175,108 @@ class AuthSystem {
} }
} }
// Обновление прав доступа для кнопок // Обновление прав доступа для кнопок и элементов интерфейса
updateButtonPermissions() { updateButtonPermissions() {
// Кнопки, доступные только администраторам // Элементы, доступные только администраторам
const adminButtons = [ const adminElements = [
'#addItemBtn', '#addItemBtn',
'#editItemBtn', '#editItemBtn',
'#deleteItemBtn', '#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); const button = $(buttonId);
if (button.length) { 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.prop('disabled', false);
button.removeAttr('title');
} else { } else {
button.prop('disabled', true); button.prop('disabled', true);
button.attr('title', 'Требуются права администратора'); button.attr('title', permission.message);
} }
} }
}); });

View File

@ -130,11 +130,6 @@ class CatalogManager {
// Заполнение фильтра категорий // Заполнение фильтра категорий
populateCategoryFilters(categories) { populateCategoryFilters(categories) {
if (!categories || categories.length === 0) {
console.warn('Нет категорий для отображения');
return;
}
const categoryFilter = $('#categoryFilter'); const categoryFilter = $('#categoryFilter');
const addItemCategory = $('#itemCategory'); const addItemCategory = $('#itemCategory');
const editItemCategory = $('#editItemCategory'); const editItemCategory = $('#editItemCategory');
@ -144,15 +139,23 @@ class CatalogManager {
addItemCategory.find('option:not(:first)').remove(); addItemCategory.find('option:not(:first)').remove();
editItemCategory.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 (categories && categories.length > 0) {
if (category && category.id && category.name) { categories.forEach(category => {
const option = `<option value="${category.id}">${category.name}</option>`; if (category && category.id && category.name) {
categoryFilter.append(option); const option = `<option value="${category.id}">${category.name}</option>`;
addItemCategory.append(option); categoryFilter.append(option);
editItemCategory.append(option); addItemCategory.append(option);
} editItemCategory.append(option);
}); }
});
}
} }
// Обработка поиска // Обработка поиска
@ -441,8 +444,8 @@ class CatalogManager {
// Получение информации о категории // Получение информации о категории
getCategoryInfo(categoryId) { getCategoryInfo(categoryId) {
if (!categoryId) { if (!categoryId || categoryId === 'uncategorized') {
return { id: 'unknown', name: 'Без категории', icon: 'fas fa-box' }; return { id: 'uncategorized', name: 'Без категории', icon: 'fas fa-box' };
} }
// Используем категории из warehouse // Используем категории из warehouse
@ -723,6 +726,9 @@ class CatalogManager {
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в навигацию // Загрузка категорий в навигацию
@ -755,18 +761,8 @@ class CatalogManager {
// Обновление прав доступа // Обновление прав доступа
updatePermissions() { updatePermissions() {
const user = auth.getCurrentUser(); // Используем централизованную систему управления правами из auth.js
if (!user) return; auth.updateButtonPermissions();
// Показываем кнопку добавления для администраторов и менеджеров
if (auth.hasRole('admin') || auth.hasRole('manager')) {
$('#addItemBtn').show();
} else {
$('#addItemBtn').hide();
}
// Показываем кнопку экспорта для всех авторизованных пользователей
$('#exportBtn').show();
} }
// Загрузка категорий в выпадающее меню // Загрузка категорий в выпадающее меню
@ -781,22 +777,61 @@ class CatalogManager {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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 // Обработка параметра категории из URL
handleCategoryFromUrl() { handleCategoryFromUrl() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);

View File

@ -350,13 +350,22 @@ class CategoryManager {
$('#currentUserInfo').text(user.name); $('#currentUserInfo').text(user.name);
$('#loginLink').hide(); $('#loginLink').hide();
$('#profileDropdown').show(); $('#profileDropdown').show();
$('#adminDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
} else {
$('#adminDropdown').hide();
}
} else { } else {
$('#currentUserInfo').text('Гость'); $('#currentUserInfo').text('Гость');
$('#loginLink').show(); $('#loginLink').show();
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в выпадающее меню // Загрузка категорий в выпадающее меню
@ -371,21 +380,60 @@ class CategoryManager {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="../catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="../catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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);
});
}
} }
// Инициализация при загрузке страницы // Инициализация при загрузке страницы

View File

@ -658,13 +658,22 @@ class HistoryManager {
$('#currentUserInfo').text(user.name); $('#currentUserInfo').text(user.name);
$('#loginLink').hide(); $('#loginLink').hide();
$('#profileDropdown').show(); $('#profileDropdown').show();
$('#adminDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
} else {
$('#adminDropdown').hide();
}
} else { } else {
$('#currentUserInfo').text('Гость'); $('#currentUserInfo').text('Гость');
$('#loginLink').show(); $('#loginLink').show();
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в навигацию // Загрузка категорий в навигацию
@ -707,21 +716,60 @@ class HistoryManager {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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);
});
}
} }
// Инициализация менеджера истории // Инициализация менеджера истории

View File

@ -494,6 +494,9 @@ class MainApp {
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в навигацию // Загрузка категорий в навигацию
@ -536,21 +539,60 @@ class MainApp {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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);
});
}
} }
// Инициализация приложения // Инициализация приложения

View File

@ -469,13 +469,22 @@ class ProfileManager {
$('#currentUserInfo').text(user.name); $('#currentUserInfo').text(user.name);
$('#loginLink').hide(); $('#loginLink').hide();
$('#profileDropdown').show(); $('#profileDropdown').show();
$('#adminDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
} else {
$('#adminDropdown').hide();
}
} else { } else {
$('#currentUserInfo').text('Гость'); $('#currentUserInfo').text('Гость');
$('#loginLink').show(); $('#loginLink').show();
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в навигацию // Загрузка категорий в навигацию
@ -518,21 +527,60 @@ class ProfileManager {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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);
});
}
} }
// Инициализация менеджера профиля // Инициализация менеджера профиля

View File

@ -705,13 +705,22 @@ class SearchManager {
$('#currentUserInfo').text(user.name); $('#currentUserInfo').text(user.name);
$('#loginLink').hide(); $('#loginLink').hide();
$('#profileDropdown').show(); $('#profileDropdown').show();
$('#adminDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
} else {
$('#adminDropdown').hide();
}
} else { } else {
$('#currentUserInfo').text('Гость'); $('#currentUserInfo').text('Гость');
$('#loginLink').show(); $('#loginLink').show();
$('#profileDropdown').hide(); $('#profileDropdown').hide();
$('#adminDropdown').hide(); $('#adminDropdown').hide();
} }
// Обновляем права доступа для элементов интерфейса
auth.updateButtonPermissions();
} }
// Загрузка категорий в навигацию // Загрузка категорий в навигацию
@ -754,21 +763,60 @@ class SearchManager {
// Добавляем активные категории // Добавляем активные категории
categories.forEach(category => { categories.forEach(category => {
if (category.active) { if (category.active) {
const isEmpty = category.itemCount === 0;
const categoryLink = ` const categoryLink = `
<li> <li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}"> <a class="dropdown-item ${isEmpty ? 'empty-category' : ''}" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i> <div class="category-info">
${category.name} <i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
<span class="category-count">${category.itemCount || 0}</span>
</a> </a>
</li> </li>
`; `;
dropdown.append(categoryLink); dropdown.append(categoryLink);
} }
}); });
// Инициализируем hover-функциональность
this.initHoverDropdown();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', 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);
});
}
} }
// Инициализация менеджера поиска // Инициализация менеджера поиска

View File

@ -36,58 +36,7 @@ class WarehouseManager {
// Получение категорий по умолчанию // Получение категорий по умолчанию
getDefaultCategories() { getDefaultCategories() {
return [ 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()
}
];
} }
// Сохранение категорий // Сохранение категорий
@ -98,7 +47,9 @@ class WarehouseManager {
// Обновление количества позиций в категориях // Обновление количества позиций в категориях
updateCategoryItemCounts() { updateCategoryItemCounts() {
this.categories.forEach(category => { 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(); this.saveCategories();
} }
@ -114,78 +65,7 @@ class WarehouseManager {
// Получение позиций по умолчанию // Получение позиций по умолчанию
getDefaultItems() { getDefaultItems() {
return [ 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'
}
];
} }
// Получение статистики // Получение статистики
@ -229,14 +109,14 @@ class WarehouseManager {
const newItem = { const newItem = {
id: this.getNextItemId(), id: this.getNextItemId(),
name: itemData.itemName, name: itemData.itemName,
category: itemData.itemCategory, category: itemData.itemCategory || 'uncategorized',
price: parseFloat(itemData.itemPrice), price: parseFloat(itemData.itemPrice),
quantity: parseInt(itemData.itemQuantity), quantity: parseInt(itemData.itemQuantity),
description: itemData.itemDescription || '', description: itemData.itemDescription || '',
barcode: itemData.itemBarcode || '', barcode: itemData.itemBarcode || '',
minQuantity: parseInt(itemData.itemQuantity) * 0.2, // 20% от начального количества minQuantity: parseInt(itemData.itemQuantity) * 0.2, // 20% от начального количества
location: 'Новый стеллаж', location: itemData.itemLocation || 'Новый стеллаж',
supplier: '', supplier: itemData.itemSupplier || '',
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
}; };
@ -244,6 +124,9 @@ class WarehouseManager {
this.items.push(newItem); this.items.push(newItem);
this.saveItems(); this.saveItems();
// Обновляем количество товаров в категории
this.updateCategoryItemCounts();
// Создаем операцию прихода для новой позиции // Создаем операцию прихода для новой позиции
await this.createOperation({ await this.createOperation({
type: 'incoming', type: 'incoming',
@ -269,6 +152,10 @@ class WarehouseManager {
}; };
this.saveItems(); this.saveItems();
// Обновляем количество товаров в категории
this.updateCategoryItemCounts();
return this.items[itemIndex]; return this.items[itemIndex];
} }
@ -281,6 +168,10 @@ class WarehouseManager {
this.items.splice(itemIndex, 1); this.items.splice(itemIndex, 1);
this.saveItems(); this.saveItems();
// Обновляем количество товаров в категории
this.updateCategoryItemCounts();
return true; return true;
} }
@ -299,6 +190,9 @@ class WarehouseManager {
item.updatedAt = new Date().toISOString(); item.updatedAt = new Date().toISOString();
this.saveItems(); this.saveItems();
// Обновляем количество товаров в категории
this.updateCategoryItemCounts();
// Создаем операцию // Создаем операцию
const operation = await this.createOperation({ const operation = await this.createOperation({
type: 'incoming', type: 'incoming',
@ -331,6 +225,9 @@ class WarehouseManager {
item.updatedAt = new Date().toISOString(); item.updatedAt = new Date().toISOString();
this.saveItems(); this.saveItems();
// Обновляем количество товаров в категории
this.updateCategoryItemCounts();
// Создаем операцию // Создаем операцию
const operation = await this.createOperation({ const operation = await this.createOperation({
type: 'outgoing', type: 'outgoing',