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 форматах
- **Настраиваемые категории**: полный 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

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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);
}
}
});

View File

@ -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() {

View File

@ -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);
});
}
}
// Инициализация при загрузке страницы

View File

@ -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);
});
}
}
// Инициализация менеджера истории

View File

@ -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);
});
}
}
// Инициализация приложения

View File

@ -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);
});
}
}
// Инициализация менеджера профиля

View File

@ -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);
});
}
}
// Инициализация менеджера поиска

View File

@ -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({