Add files via upload

v0.0.3
This commit is contained in:
cyrteL
2025-07-28 17:31:27 +03:00
committed by GitHub
parent 119a74ad98
commit 4a6868ced6
17 changed files with 2395 additions and 292 deletions

182
README.md
View File

@ -1,18 +1,27 @@
# Система управления складом "Склад-Менеджер"
Современная веб-система для управления товарными запасами и операциями склада с разными уровнями доступа.
Современная веб-система для управления складом деталей и операциями с разными уровнями доступа.
## 🚀 Возможности системы
### Основной функционал
- **Управление товарами**: добавление, редактирование, удаление товаров
- **Операции склада**: приход и расход товаров
- **Каталог товаров**: удобный просмотр с фильтрами и поиском
- **Управление позициями**: добавление, редактирование, удаление позиций
- **Операции склада**: приход и расход позиций
- **Каталог позиций**: удобный просмотр с фильтрами и поиском
- **История операций**: полный учет всех операций со складом
- **Поиск**: быстрый поиск товаров и операций
- **Поиск**: быстрый поиск позиций и операций
- **Отчеты**: генерация различных отчетов
- **Экспорт данных**: выгрузка в CSV и JSON форматах
### Система безопасности
- **Многоуровневая авторизация** с разными ролями
- **Контроль доступа** к функциям системы
- **Безопасные сессии** пользователей
- **Валидация данных** на всех уровнях
- **Проверка прав доступа** для каждой операции
- **Безопасная обработка ошибок** с информативными уведомлениями
- **URL валидация** параметров и фильтров
### Уровни доступа
#### 🔧 Администратор (admin/admin123)
@ -53,10 +62,11 @@ warehouse-system/
│ ├── auth.js # Система авторизации
│ ├── main.js # Основная логика
│ ├── warehouse.js # Управление складом
│ ├── catalog.js # Каталог товаров
│ ├── catalog.js # Каталог товаров (с проверкой прав доступа)
│ ├── history.js # История операций
│ ├── search.js # Поиск
── profile.js # Профиль пользователя
── profile.js # Профиль пользователя
│ └── categories.js # Управление категориями
└── README.md # Документация
```
@ -72,7 +82,22 @@ warehouse-system/
## 🚀 Быстрый старт
### 1. Первый вход
### 1. Установка
1. Скачайте или клонируйте проект
2. Откройте `index.html` в браузере
3. Или запустите локальный сервер:
```bash
# Python 3
python -m http.server 8000
# Node.js
npx http-server
# PHP
php -S localhost:8000
```
### 2. Первый вход
1. Откройте `login.html`
2. Используйте тестовые учетные записи:
- **Администратор**: `admin` / `admin123`
@ -80,7 +105,7 @@ warehouse-system/
- **Оператор**: `operator` / `operator123`
- **Наблюдатель**: `viewer` / `viewer123`
### 2. Начальная настройка
### 3. Начальная настройка
1. Войдите как администратор
2. Добавьте первые товары через главную страницу
3. Настройте категории товаров
@ -92,12 +117,24 @@ warehouse-system/
- **Быстрые действия**: добавление товаров, приход/расход
- **Статистика**: общая информация о складе
- **Последние операции**: мониторинг активности
- **Быстрая навигация**: выпадающее меню категорий
### Каталог товаров
- **Просмотр**: все товары с фильтрами
### Навигация по категориям
- **Выпадающее меню**: появляется при наведении на "Каталог"
- **Быстрый переход**: клик по категории открывает отфильтрованный каталог
- **URL фильтрация**: прямые ссылки вида `catalog.html?category=electronics`
- **Визуальная обратная связь**: иконки и цвета категорий
- **Единообразие**: одинаковое меню на всех страницах
### Каталог позиций
- **Просмотр**: все позиции с фильтрами и статистикой
- **Поиск**: по названию, описанию, штрих-коду
- **Фильтры**: по категории, цене, наличию
- **Редактирование**: изменение информации о товарах
- **Фильтры**: по категории, цене, наличию, статусу запасов
- **Редактирование**: изменение информации о позициях (только для админов и менеджеров)
- **Экспорт**: выгрузка каталога в CSV формате
- **Безопасность**: проверка прав доступа для всех операций
- **Быстрая навигация**: выпадающее меню категорий при наведении
- **URL фильтрация**: прямая ссылка на отфильтрованный каталог
### История операций
- **Просмотр**: все операции с детализацией
@ -106,10 +143,11 @@ warehouse-system/
- **Экспорт**: выгрузка данных
### Поиск
- **Универсальный поиск**: товары и операции
- **Универсальный поиск**: позиции и операции
- **Расширенные фильтры**: точная настройка
- **Результаты**: с подсветкой найденного
- **Экспорт результатов**: сохранение найденного
- **Быстрая навигация**: переход к категориям из результатов поиска
### Профиль пользователя
- **Информация**: личные данные и права
@ -126,9 +164,11 @@ warehouse-system/
4. Создавайте, редактируйте, удаляйте пользователей
### Настройка категорий
1. Откройте "Категории товаров"
1. Откройте "Категории позиций"
2. Добавьте нужные категории
3. Настройте иконки и описания
4. Категории автоматически появятся в выпадающем меню
5. Изменения применяются мгновенно на всех страницах
### Генерация отчетов
1. Выберите тип отчета
@ -138,7 +178,7 @@ warehouse-system/
## 📊 Структура данных
### Товар
### Позиция
```javascript
{
id: 1,
@ -151,8 +191,8 @@ warehouse-system/
minQuantity: 10,
location: "Стеллаж A-1",
supplier: "Поставщик",
createdAt: "2024-01-15T10:00:00Z",
updatedAt: "2024-01-15T10:00:00Z"
createdAt: "2025-01-15T10:00:00Z",
updatedAt: "2025-01-15T10:00:00Z"
}
```
@ -166,7 +206,7 @@ warehouse-system/
quantity: 10,
employeeId: 1,
employeeName: "Имя сотрудника",
date: "2024-01-15T10:00:00Z",
date: "2025-01-15T10:00:00Z",
status: "completed",
supplier: "Поставщик",
recipient: "Получатель",
@ -188,6 +228,21 @@ warehouse-system/
}
```
### Категория
```javascript
{
id: "electronics",
name: "Электроника",
description: "Электронные компоненты и устройства",
icon: "fas fa-microchip",
color: "#2c5aa0",
active: true,
itemCount: 25,
createdAt: "2025-01-15T10:00:00Z",
updatedAt: "2025-01-15T10:00:00Z"
}
```
## 🎨 Кастомизация
### Изменение цветовой схемы
@ -209,7 +264,20 @@ warehouse-system/
{
id: 'new-category',
name: 'Новая категория',
icon: 'fas fa-icon-name'
icon: 'fas fa-icon-name',
color: '#2c5aa0',
description: 'Описание категории',
active: true
}
```
### Настройка выпадающего меню
Категории автоматически появляются в выпадающем меню навигации. Для настройки стилей отредактируйте CSS в `css/style.css`:
```css
#catalogCategoriesDropdown {
min-width: 250px;
max-height: 400px;
overflow-y: auto;
}
```
@ -232,6 +300,50 @@ warehouse-system/
sudo certbot --nginx -d your-domain.com
```
## 📈 Производительность
### Оптимизация
- Используйте современный браузер
- Включите кэширование
- Минимизируйте количество одновременных операций
- Регулярно очищайте старые данные
### Масштабирование
- Для больших объемов данных используйте базу данных
- Добавьте серверную часть (PHP, Node.js, Python)
- Используйте CDN для статических файлов
- Настройте кэширование на сервере
- Оптимизируйте загрузку категорий для больших каталогов
### Технические улучшения
- **Безопасная обработка данных**: проверка на null/undefined
- **Валидация форм**: клиентская проверка ввода
- **Обработка ошибок**: информативные уведомления
- **Права доступа**: детальная проверка ролей
- **Оптимизация производительности**: эффективная фильтрация
- **URL маршрутизация**: обработка параметров и фильтров
- **Асинхронная загрузка**: динамическое обновление меню
- **Кэширование данных**: оптимизация производительности
## 🔄 Последние обновления
### Версия 1.2.0 (Январь 2025)
-**Добавлено выпадающее меню категорий**: быстрая навигация по категориям
-**Реализована URL фильтрация**: прямые ссылки на отфильтрованный каталог
-**Улучшена навигация**: единообразное меню на всех страницах
-**Добавлены анимации**: плавные эффекты появления меню
-**Улучшен UX**: hover эффекты и визуальная обратная связь
-**Расширена функциональность**: переходы между категориями из любого места
### Версия 1.1.0 (Январь 2025)
-**Улучшена безопасность каталога**: добавлена проверка прав доступа
-**Исправлена навигация**: добавлена ссылка "Поиск" в главное меню
-**Улучшена обработка ошибок**: безопасная работа с пустыми данными
-**Добавлена валидация**: проверка на null/undefined значения
-**Улучшена статистика**: безопасный подсчет с fallback значениями
-**Исправлены отступы**: консистентная структура кода
-**Добавлены уведомления**: информативные сообщения об ошибках
## 🐛 Устранение неполадок
### Частые проблемы
@ -240,6 +352,7 @@ sudo certbot --nginx -d your-domain.com
- Проверьте правильность логина/пароля
- Очистите кэш браузера
- Проверьте консоль на ошибки JavaScript
- Убедитесь, что JavaScript включен в браузере
#### Не сохраняются данные
- Проверьте доступ к LocalStorage
@ -251,6 +364,14 @@ sudo certbot --nginx -d your-domain.com
- Очистите кэш браузера
- Проверьте количество товаров/операций
#### Проблемы с каталогом
- Проверьте права доступа пользователя
- Убедитесь, что категории загружены корректно
- Проверьте консоль на ошибки загрузки данных
- Очистите фильтры, если каталог пустой
- Проверьте URL параметры для фильтрации по категориям
- Убедитесь, что выпадающее меню категорий загружается
### Логи и отладка
Откройте консоль разработчика (F12) для просмотра ошибок и логов.
@ -271,17 +392,22 @@ sudo certbot --nginx -d your-domain.com
## 📄 Лицензия
Этот проект распространяется под лицензией MIT.
Этот проект распространяется под лицензией MIT. См. файл LICENSE для подробностей.
## 👥 Авторы
- Разработка: Roman Pylaev
- Дизайн: Roman Pylaev
- Тестирование: Roman Pylaev
- Разработка: [Ваше имя]
- Дизайн: [Имя дизайнера]
- Тестирование: [Имя тестировщика]
## 📞 Контакты
- Email: r0manpylaev@yandex.ru
- Телефон: +7 (977) 665-45-78
- Telegram: @dark7es
- Discord: @dark7es
- Email: support@warehouse-system.com
- Телефон: +7 (XXX) XXX-XX-XX
- Веб-сайт: https://warehouse-system.com
---
**Версия**: 1.2.0
**Дата**: Январь 2025
**Последнее обновление**: 15.01.2025

338
admin/categories.html Normal file
View File

@ -0,0 +1,338 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Управление категориями - Склад-Менеджер</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
</head>
<body>
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="../index.html">
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="../catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="../history.html">
<i class="fas fa-history me-1"></i>История
</a>
</li>
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="users.html">Управление пользователями</a></li>
<li><a class="dropdown-item active" href="categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="../profile.html">Мой профиль</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" id="logoutBtn">Выйти</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link" href="../index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Основной контент -->
<main class="container mt-4">
<!-- Заголовок страницы -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="mb-1">
<i class="fas fa-tags me-2"></i>
Управление категориями
</h1>
<p class="text-muted mb-0">Создание и редактирование категорий позиций</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
<i class="fas fa-plus me-1"></i>Добавить категорию
</button>
</div>
</div>
</div>
</div>
<!-- Статистика категорий -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="totalCategories">0</h4>
<p class="card-text">Всего категорий</p>
</div>
<i class="fas fa-tags fa-2x"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="totalItems">0</h4>
<p class="card-text">Всего позиций</p>
</div>
<i class="fas fa-boxes fa-2x"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="activeCategories">0</h4>
<p class="card-text">Активные категории</p>
</div>
<i class="fas fa-check-circle fa-2x"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="emptyCategories">0</h4>
<p class="card-text">Пустые категории</p>
</div>
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Таблица категорий -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>
Список категорий
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="categoriesTable">
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Описание</th>
<th>Иконка</th>
<th>Количество позиций</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<!-- Категории будут загружены динамически -->
</tbody>
</table>
</div>
</div>
</div>
</main>
<!-- Модальное окно добавления категории -->
<div class="modal fade" id="addCategoryModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-plus me-2"></i>
Добавить категорию
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addCategoryForm">
<div class="mb-3">
<label for="categoryName" class="form-label">Название категории *</label>
<input type="text" class="form-control" id="categoryName" required>
</div>
<div class="mb-3">
<label for="categoryDescription" class="form-label">Описание</label>
<textarea class="form-control" id="categoryDescription" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="categoryIcon" class="form-label">Иконка</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-icons"></i>
</span>
<input type="text" class="form-control" id="categoryIcon" placeholder="fas fa-box">
</div>
<small class="form-text text-muted">Используйте классы Font Awesome (например: fas fa-box)</small>
</div>
<div class="mb-3">
<label for="categoryColor" class="form-label">Цвет категории</label>
<input type="color" class="form-control form-control-color" id="categoryColor" value="#2c5aa0">
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="categoryActive" checked>
<label class="form-check-label" for="categoryActive">
Активная категория
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="saveCategoryBtn">
<i class="fas fa-save me-1"></i>Сохранить
</button>
</div>
</div>
</div>
</div>
<!-- Модальное окно редактирования категории -->
<div class="modal fade" id="editCategoryModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-edit me-2"></i>
Редактировать категорию
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editCategoryForm">
<input type="hidden" id="editCategoryId">
<div class="mb-3">
<label for="editCategoryName" class="form-label">Название категории *</label>
<input type="text" class="form-control" id="editCategoryName" required>
</div>
<div class="mb-3">
<label for="editCategoryDescription" class="form-label">Описание</label>
<textarea class="form-control" id="editCategoryDescription" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="editCategoryIcon" class="form-label">Иконка</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-icons"></i>
</span>
<input type="text" class="form-control" id="editCategoryIcon" placeholder="fas fa-box">
</div>
<small class="form-text text-muted">Используйте классы Font Awesome (например: fas fa-box)</small>
</div>
<div class="mb-3">
<label for="editCategoryColor" class="form-label">Цвет категории</label>
<input type="color" class="form-control form-control-color" id="editCategoryColor" value="#2c5aa0">
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editCategoryActive">
<label class="form-check-label" for="editCategoryActive">
Активная категория
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="updateCategoryBtn">
<i class="fas fa-save me-1"></i>Обновить
</button>
</div>
</div>
</div>
</div>
<!-- Подвал -->
<footer class="bg-dark text-white mt-5 py-4">
<div class="container">
<div class="row">
<div class="col-md-6">
<h5>Система управления складом</h5>
<p class="mb-0">Эффективное управление складом деталей</p>
</div>
<div class="col-md-6 text-md-end">
<p class="mb-0">&copy; 2025 Склад-Менеджер. Все права защищены.</p>
</div>
</div>
</div>
</footer>
<!-- Скрипты -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="../js/auth.js"></script>
<script src="../js/warehouse.js"></script>
<script src="../js/categories.js"></script>
</body>
</html>

View File

@ -9,27 +9,46 @@
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- Навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="index.html">
<i class="fas fa-warehouse me-2"></i>
Склад-Менеджер
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="catalog.html">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle active" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="history.html">
@ -41,19 +60,17 @@
<i class="fas fa-search me-1"></i>Поиск
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown">
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
@ -68,6 +85,24 @@
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Основной контент -->
<main class="container mt-4">
<!-- Заголовок страницы -->
@ -77,16 +112,17 @@
<div>
<h1 class="mb-1">
<i class="fas fa-boxes me-2"></i>
Каталог товаров
Каталог позиций
</h1>
<p class="text-muted mb-0">Управление товарными позициями склада</p>
<p class="text-muted mb-0">Управление позициями склада</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" id="exportBtn">
<i class="fas fa-download me-1"></i>Экспорт
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn">
<i class="fas fa-plus me-1"></i>Добавить товар
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn"
style="display: none;" title="Требуются права администратора или менеджера">
<i class="fas fa-plus me-1"></i>Добавить позицию
</button>
</div>
</div>
@ -97,7 +133,7 @@
<div class="catalog-filters">
<div class="row">
<div class="col-md-4 mb-3">
<label for="searchInput" class="form-label">Поиск товаров</label>
<label for="searchInput" class="form-label">Поиск позиций</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-search"></i>
@ -154,7 +190,7 @@
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="totalItemsCount">0</h4>
<p class="card-text">Всего товаров</p>
<p class="card-text">Всего позиций</p>
</div>
<i class="fas fa-boxes fa-2x"></i>
</div>
@ -400,4 +436,4 @@
<script src="js/warehouse.js"></script>
<script src="js/catalog.js"></script>
</body>
</html>
</html>

View File

@ -1,17 +1,20 @@
/* Основные стили для системы управления складом */
:root {
--primary-color: #0d6efd;
--primary-color: #2c5aa0;
--secondary-color: #6c757d;
--success-color: #198754;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #0dcaf0;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #212529;
--border-radius: 0.375rem;
--box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--transition: all 0.15s ease-in-out;
--dark-color: #343a40;
--border-radius: 0.25rem;
--box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
--transition: all 0.2s ease-in-out;
--header-bg: #1e3a5f;
--sidebar-bg: #f8f9fa;
--card-border: #e9ecef;
}
/* Общие стили */
@ -21,40 +24,124 @@ body {
line-height: 1.6;
}
/* Навигация */
/* Главная навигация */
.navbar {
background: #fff !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-bottom: 1px solid var(--card-border);
padding: 1rem 0;
}
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
color: var(--primary-color) !important;
}
.navbar-search .input-group {
border-radius: 25px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.navbar-search .form-control {
border: none;
padding: 0.75rem 1.25rem;
font-size: 0.95rem;
}
.navbar-search .btn {
border: none;
padding: 0.75rem 1.25rem;
border-radius: 0 25px 25px 0;
}
.navbar-nav .nav-link {
font-weight: 500;
transition: var(--transition);
padding: 0.5rem 1rem !important;
border-radius: var(--border-radius);
margin: 0 0.25rem;
color: var(--dark-color) !important;
}
.navbar-nav .nav-link:hover {
color: rgba(255, 255, 255, 0.8) !important;
background-color: rgba(44, 90, 160, 0.1);
color: var(--primary-color) !important;
}
.navbar-nav .nav-link.active {
color: var(--primary-color) !important;
background-color: rgba(44, 90, 160, 0.15);
font-weight: 600;
}
/* Панель категорий */
.categories-bar {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid var(--card-border);
padding: 0.5rem 0;
}
.categories-nav {
gap: 0.5rem;
}
.categories-nav .nav-link {
border-radius: 20px;
padding: 0.5rem 1rem;
font-weight: 500;
transition: var(--transition);
color: var(--dark-color);
border: 1px solid transparent;
}
.categories-nav .nav-link:hover {
background-color: rgba(44, 90, 160, 0.1);
border-color: rgba(44, 90, 160, 0.2);
color: var(--primary-color);
}
.categories-nav .nav-link.active {
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
color: #fff;
border-color: var(--primary-color);
box-shadow: 0 2px 8px rgba(44, 90, 160, 0.3);
}
/* Карточки быстрых действий */
.quick-action-card {
transition: var(--transition);
border: 1px solid rgba(0, 0, 0, 0.125);
border: 1px solid var(--card-border);
box-shadow: var(--box-shadow);
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
border-radius: 12px;
}
.quick-action-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
border-color: var(--primary-color);
}
.quick-action-card .card-body {
padding: 2rem 1.5rem;
}
.quick-action-card i {
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Градиентный фон для приветственного блока */
.bg-gradient-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%);
background: linear-gradient(135deg, var(--header-bg) 0%, var(--primary-color) 100%);
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
/* Статистические карточки */
@ -65,35 +152,56 @@ body {
border: none;
box-shadow: var(--box-shadow);
transition: var(--transition);
border-radius: 12px;
overflow: hidden;
}
.card.bg-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%) !important;
}
.card.bg-success {
background: linear-gradient(135deg, var(--success-color) 0%, #1e7e34 100%) !important;
}
.card.bg-warning {
background: linear-gradient(135deg, var(--warning-color) 0%, #e0a800 100%) !important;
}
.card.bg-info {
background: linear-gradient(135deg, var(--info-color) 0%, #138496 100%) !important;
}
.card.bg-primary:hover,
.card.bg-success:hover,
.card.bg-warning:hover,
.card.bg-info:hover {
transform: translateY(-2px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
/* Таблицы */
.table {
background-color: white;
border-radius: var(--border-radius);
border-radius: 12px;
overflow: hidden;
box-shadow: var(--box-shadow);
border: 1px solid var(--card-border);
}
.table thead th {
background-color: var(--light-color);
border-bottom: 2px solid #dee2e6;
background: linear-gradient(135deg, var(--light-color) 0%, #e9ecef 100%);
border-bottom: 2px solid var(--primary-color);
font-weight: 600;
text-transform: uppercase;
font-size: 0.875rem;
letter-spacing: 0.5px;
color: var(--dark-color);
}
.table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
background-color: rgba(44, 90, 160, 0.05);
transition: var(--transition);
}
/* Модальные окна */
@ -115,15 +223,23 @@ body {
/* Формы */
.form-control,
.form-select {
border-radius: var(--border-radius);
border-radius: 8px;
border: 1px solid #ced4da;
transition: var(--transition);
padding: 0.75rem 1rem;
}
.form-control:focus,
.form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
box-shadow: 0 0 0 0.2rem rgba(44, 90, 160, 0.25);
background-color: #fff;
}
.input-group-text {
border-radius: 8px 0 0 8px;
border: 1px solid #ced4da;
background: linear-gradient(135deg, var(--light-color) 0%, #e9ecef 100%);
}
.form-label {
@ -134,15 +250,26 @@ body {
/* Кнопки */
.btn {
border-radius: var(--border-radius);
border-radius: 8px;
font-weight: 500;
transition: var(--transition);
text-transform: none;
border: none;
padding: 0.5rem 1.25rem;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, #1e3a5f 100%);
border: none;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1e3a5f 0%, var(--primary-color) 100%);
}
.btn-primary {
@ -440,4 +567,57 @@ footer {
border: 1px solid #000 !important;
box-shadow: none !important;
}
}
/* Стили для выпадающего меню категорий */
#catalogCategoriesDropdown {
min-width: 250px;
max-height: 400px;
overflow-y: auto;
border: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
border-radius: 12px;
padding: 0.5rem 0;
}
#catalogCategoriesDropdown .dropdown-item {
padding: 0.75rem 1rem;
border-radius: 8px;
margin: 0 0.5rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
#catalogCategoriesDropdown .dropdown-item:hover {
background-color: var(--primary-color);
color: white;
transform: translateX(2px);
}
#catalogCategoriesDropdown .dropdown-item i {
width: 16px;
text-align: center;
}
#catalogCategoriesDropdown .dropdown-divider {
margin: 0.5rem 1rem;
border-color: #e9ecef;
}
/* Анимация появления выпадающего меню */
.dropdown-menu.show {
animation: dropdownFadeIn 0.2s ease-out;
}
@keyframes dropdownFadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -9,51 +9,63 @@
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- Навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="index.html">
<i class="fas fa-warehouse me-2"></i>
Склад-Менеджер
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="catalog.html">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link active" href="history.html">
<i class="fas fa-history me-1"></i>История
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="search.html">
<i class="fas fa-search me-1"></i>Поиск
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown">
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
@ -68,6 +80,24 @@
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Основной контент -->
<main class="container mt-4">
<!-- Заголовок страницы -->
@ -377,4 +407,4 @@
<script src="js/warehouse.js"></script>
<script src="js/history.js"></script>
</body>
</html>
</html>

View File

@ -9,27 +9,41 @@
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- Навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="index.html">
<i class="fas fa-warehouse me-2"></i>
Склад-Менеджер
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="catalog.html">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="history.html">
@ -41,19 +55,17 @@
<i class="fas fa-search me-1"></i>Поиск
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown">
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
@ -68,6 +80,24 @@
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link active" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Основной контент -->
<main class="container mt-4">
<!-- Приветственный блок -->
@ -79,7 +109,7 @@
<i class="fas fa-warehouse me-3"></i>
Добро пожаловать в систему управления складом
</h1>
<p class="card-text">Эффективное управление товарными запасами и операциями склада</p>
<p class="card-text">Эффективное управление складом деталей и операциями</p>
</div>
</div>
</div>
@ -94,8 +124,8 @@
<div class="card h-100 quick-action-card">
<div class="card-body text-center">
<i class="fas fa-plus-circle fa-3x text-primary mb-3"></i>
<h5 class="card-title">Добавить товар</h5>
<p class="card-text">Добавить новый товар в каталог</p>
<h5 class="card-title">Добавить позицию</h5>
<p class="card-text">Добавить новую позицию в каталог</p>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
Добавить
</button>
@ -107,7 +137,7 @@
<div class="card-body text-center">
<i class="fas fa-box-open fa-3x text-success mb-3"></i>
<h5 class="card-title">Приход</h5>
<p class="card-text">Оформить приход товара</p>
<p class="card-text">Оформить приход позиции</p>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#incomingModal">
Оформить
</button>
@ -119,7 +149,7 @@
<div class="card-body text-center">
<i class="fas fa-truck fa-3x text-warning mb-3"></i>
<h5 class="card-title">Расход</h5>
<p class="card-text">Оформить расход товара</p>
<p class="card-text">Оформить расход позиции</p>
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#outgoingModal">
Оформить
</button>
@ -131,13 +161,57 @@
<div class="card-body text-center">
<i class="fas fa-search fa-3x text-info mb-3"></i>
<h5 class="card-title">Поиск</h5>
<p class="card-text">Найти товар или операцию</p>
<p class="card-text">Найти позицию или операцию</p>
<a href="search.html" class="btn btn-info">Найти</a>
</div>
</div>
</div>
</div>
<!-- Дополнительные действия -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card h-100 quick-action-card">
<div class="card-body text-center">
<i class="fas fa-user fa-3x text-secondary mb-3"></i>
<h5 class="card-title">Мой профиль</h5>
<p class="card-text">Управление профилем и настройками</p>
<a href="profile.html" class="btn btn-secondary">Открыть</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 quick-action-card">
<div class="card-body text-center">
<i class="fas fa-history fa-3x text-dark mb-3"></i>
<h5 class="card-title">История операций</h5>
<p class="card-text">Просмотр истории всех операций</p>
<a href="history.html" class="btn btn-dark">Просмотреть</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 quick-action-card">
<div class="card-body text-center">
<i class="fas fa-boxes fa-3x text-primary mb-3"></i>
<h5 class="card-title">Каталог позиций</h5>
<p class="card-text">Просмотр и управление позициями</p>
<a href="catalog.html" class="btn btn-primary">Открыть</a>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100 quick-action-card">
<div class="card-body text-center">
<i class="fas fa-cog fa-3x text-warning mb-3"></i>
<h5 class="card-title">Администрирование</h5>
<p class="card-text">Управление системой и пользователями</p>
<a href="admin/categories.html" class="btn btn-warning">Управлять</a>
</div>
</div>
</div>
</div>
<!-- Статистика -->
<div class="row mb-4">
<div class="col-12">
@ -149,7 +223,7 @@
<div class="d-flex justify-content-between">
<div>
<h4 class="card-title" id="totalItems">0</h4>
<p class="card-text">Всего товаров</p>
<p class="card-text">Всего позиций</p>
</div>
<i class="fas fa-boxes fa-2x"></i>
</div>
@ -247,7 +321,7 @@
<form id="addItemForm">
<div class="row">
<div class="col-md-6 mb-3">
<label for="itemName" class="form-label">Название товара *</label>
<label for="itemName" class="form-label">Название позиции *</label>
<input type="text" class="form-control" id="itemName" required>
</div>
<div class="col-md-6 mb-3">
@ -305,9 +379,9 @@
<div class="modal-body">
<form id="incomingForm">
<div class="mb-3">
<label for="incomingItem" class="form-label">Товар *</label>
<label for="incomingItem" class="form-label">Позиция *</label>
<select class="form-select" id="incomingItem" required>
<option value="">Выберите товар</option>
<option value="">Выберите позицию</option>
</select>
</div>
<div class="mb-3">
@ -348,9 +422,9 @@
<div class="modal-body">
<form id="outgoingForm">
<div class="mb-3">
<label for="outgoingItem" class="form-label">Товар *</label>
<label for="outgoingItem" class="form-label">Позиция *</label>
<select class="form-select" id="outgoingItem" required>
<option value="">Выберите товар</option>
<option value="">Выберите позицию</option>
</select>
</div>
<div class="mb-3">
@ -383,7 +457,7 @@
<div class="row">
<div class="col-md-6">
<h5>Система управления складом</h5>
<p class="mb-0">Эффективное управление товарными запасами</p>
<p class="mb-0">Эффективное управление складом деталей</p>
</div>
<div class="col-md-6 text-md-end">
<p class="mb-0">&copy; 2025 Склад-Менеджер. Все права защищены.</p>
@ -399,4 +473,4 @@
<script src="js/main.js"></script>
<script src="js/warehouse.js"></script>
</body>
</html>
</html>

View File

@ -39,14 +39,17 @@ class AuthSystem {
try {
this.currentUser = JSON.parse(userData);
this.isAuthenticated = true;
console.log('Пользователь авторизован:', this.currentUser);
this.updateUI();
} catch (error) {
console.error('Ошибка при загрузке данных пользователя:', error);
this.logout();
}
} else {
console.log('Пользователь не авторизован');
// Если нет сессии и мы не на странице входа, перенаправляем
if (!window.location.pathname.includes('login.html')) {
console.log('Перенаправление на страницу входа');
window.location.href = 'login.html';
}
}
@ -54,8 +57,8 @@ class AuthSystem {
// Вход в систему
login() {
const username = $('#username').val();
const password = $('#password').val();
const username = $('#username').val().trim();
const password = $('#password').val().trim();
if (!username || !password) {
this.showNotification('Пожалуйста, заполните все поля', 'warning');
@ -77,11 +80,24 @@ class AuthSystem {
this.showNotification('Успешный вход в систему', 'success');
// Скрываем индикатор загрузки и восстанавливаем кнопку
$('#loginLoading').hide();
$('#loginBtn').prop('disabled', false);
// Перенаправляем на главную страницу
setTimeout(() => {
window.location.href = 'index.html';
}, 1000);
} else {
// Скрываем индикатор загрузки и восстанавливаем кнопку
$('#loginLoading').hide();
$('#loginBtn').prop('disabled', false);
// Показываем ошибку в полях
$('#username').addClass('is-invalid');
$('#password').addClass('is-invalid');
$('#usernameError').text('Неверное имя пользователя или пароль').show();
this.showNotification('Неверное имя пользователя или пароль', 'danger');
}
}
@ -101,18 +117,29 @@ class AuthSystem {
// Обновление интерфейса в зависимости от прав доступа
updateUI() {
if (!this.currentUser) return;
if (!this.currentUser) {
console.log('Нет текущего пользователя для обновления UI');
return;
}
console.log('Обновление UI для пользователя:', this.currentUser.name);
// Обновляем информацию о пользователе в навигации
$('#profileDropdown .dropdown-toggle').html(`
<i class="fas fa-user me-1"></i>${this.currentUser.name}
`);
const profileToggle = $('#profileDropdown .dropdown-toggle');
if (profileToggle.length) {
profileToggle.html(`<i class="fas fa-user me-1"></i>${this.currentUser.name}`);
}
// Показываем профиль для всех авторизованных пользователей
$('#profileDropdown').show();
// Показываем/скрываем элементы администрирования
if (this.hasRole('admin')) {
$('#adminDropdown').show();
console.log('Показана панель администратора');
} else {
$('#adminDropdown').hide();
console.log('Скрыта панель администратора');
}
// Обновляем права доступа для кнопок
@ -188,7 +215,7 @@ class AuthSystem {
// Проверка аутентификации
isUserAuthenticated() {
return this.isAuthenticated;
return this.isAuthenticated && this.currentUser !== null;
}
// Получение списка пользователей (имитация базы данных)

View File

@ -1,4 +1,4 @@
// JavaScript для страницы каталога товаров
// JavaScript для страницы каталога позиций
class CatalogManager {
constructor() {
@ -14,10 +14,45 @@ class CatalogManager {
}
init() {
$(document).ready(() => {
this.loadCatalog();
this.bindEvents();
this.initializeFilters();
$(document).ready(async () => {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = 'login.html';
return;
}
console.log('Инициализация каталога для пользователя:', auth.getCurrentUser()?.name);
// Загружаем каталог
await this.loadCatalog();
// Загружаем категории в навигацию
await this.loadCategoriesNav();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
// Обновляем информацию о пользователе
this.updateUserInfo();
// Привязываем события
this.bindEvents();
// Инициализируем фильтры
this.initializeFilters();
// Проверяем параметр категории в URL
this.handleCategoryFromUrl();
// Обновляем права доступа
this.updatePermissions();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
});
}
@ -41,6 +76,13 @@ class CatalogManager {
// Экспорт
$('#exportBtn').on('click', () => this.exportCatalog());
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
}
// Загрузка каталога
@ -48,12 +90,17 @@ class CatalogManager {
try {
this.showLoading();
// Загружаем товары
// Загружаем позиции
this.currentItems = await this.warehouse.getAllItems();
// Загружаем категории
const categories = await this.warehouse.getCategories();
this.populateCategoryFilters(categories);
if (categories && categories.length > 0) {
this.populateCategoryFilters(categories);
} else {
console.warn('Категории не найдены');
auth.showNotification('Категории не загружены', 'warning');
}
// Применяем фильтры и рендерим
this.applyFilters();
@ -83,6 +130,11 @@ class CatalogManager {
// Заполнение фильтра категорий
populateCategoryFilters(categories) {
if (!categories || categories.length === 0) {
console.warn('Нет категорий для отображения');
return;
}
const categoryFilter = $('#categoryFilter');
const addItemCategory = $('#itemCategory');
const editItemCategory = $('#editItemCategory');
@ -94,10 +146,12 @@ class CatalogManager {
// Добавляем категории
categories.forEach(category => {
const option = `<option value="${category.id}">${category.name}</option>`;
categoryFilter.append(option);
addItemCategory.append(option);
editItemCategory.append(option);
if (category && category.id && category.name) {
const option = `<option value="${category.id}">${category.name}</option>`;
categoryFilter.append(option);
addItemCategory.append(option);
editItemCategory.append(option);
}
});
}
@ -125,7 +179,7 @@ class CatalogManager {
this.filters.inStock = $('#inStockFilter').is(':checked');
this.filters.lowStock = $('#lowStockFilter').is(':checked');
// Фильтруем товары
// Фильтруем позиции
this.filteredItems = this.currentItems.filter(item => {
// Поиск по названию, описанию и штрих-коду
if (this.filters.search) {
@ -191,7 +245,7 @@ class CatalogManager {
<div class="col-12">
<div class="empty-state">
<i class="fas fa-search"></i>
<h4>Товары не найдены</h4>
<h4>Позиции не найдены</h4>
<p>Попробуйте изменить параметры поиска или фильтры</p>
</div>
</div>
@ -205,7 +259,7 @@ class CatalogManager {
const endIndex = startIndex + this.itemsPerPage;
const pageItems = this.filteredItems.slice(startIndex, endIndex);
// Рендерим товары
// Рендерим позиции
pageItems.forEach(item => {
const itemHtml = this.currentView === 'grid' ?
this.createGridItem(item) : this.createListItem(item);
@ -216,7 +270,7 @@ class CatalogManager {
this.renderPagination(totalPages);
}
// Создание карточки товара (сетка)
// Создание карточки позиции (сетка)
createGridItem(item) {
const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item);
@ -248,7 +302,7 @@ class CatalogManager {
<button class="btn btn-outline-primary btn-sm" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-1"></i>Просмотр
</button>
${auth.hasPermission('write') ? `
${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
<button class="btn btn-outline-warning btn-sm" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-1"></i>Редактировать
</button>
@ -260,7 +314,7 @@ class CatalogManager {
`;
}
// Создание строки товара (список)
// Создание строки позиции (список)
createListItem(item) {
const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item);
@ -300,12 +354,12 @@ class CatalogManager {
<li><a class="dropdown-item" href="#" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-2"></i>Просмотр
</a></li>
${auth.hasPermission('write') ? `
${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
<li><a class="dropdown-item" href="#" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-2"></i>Редактировать
</a></li>
` : ''}
${auth.hasPermission('delete') ? `
${auth.hasRole('admin') ? `
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="catalogManager.deleteItem(${item.id})">
<i class="fas fa-trash me-2"></i>Удалить
@ -370,10 +424,14 @@ class CatalogManager {
// Обновление статистики
updateStatistics() {
if (!this.filteredItems) {
this.filteredItems = [];
}
const totalItems = this.filteredItems.length;
const inStockItems = this.filteredItems.filter(item => item.quantity > 0).length;
const lowStockItems = this.filteredItems.filter(item => item.quantity <= item.minQuantity).length;
const categories = [...new Set(this.filteredItems.map(item => item.category))].length;
const inStockItems = this.filteredItems.filter(item => item && item.quantity > 0).length;
const lowStockItems = this.filteredItems.filter(item => item && item.quantity <= (item.minQuantity || 0)).length;
const categories = [...new Set(this.filteredItems.filter(item => item && item.category).map(item => item.category))].length;
$('#totalItemsCount').text(totalItems);
$('#inStockCount').text(inStockItems);
@ -383,17 +441,21 @@ class CatalogManager {
// Получение информации о категории
getCategoryInfo(categoryId) {
const categories = [
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' },
{ id: 'clothing', name: 'Одежда', icon: 'fas fa-tshirt' },
{ id: 'tools', name: 'Инструменты', icon: 'fas fa-tools' },
{ id: 'office', name: 'Офисные принадлежности', icon: 'fas fa-briefcase' },
{ id: 'furniture', name: 'Мебель', icon: 'fas fa-couch' },
{ id: 'books', name: 'Книги', icon: 'fas fa-book' }
];
if (!categoryId) {
return { id: 'unknown', name: 'Без категории', icon: 'fas fa-box' };
}
// Используем категории из warehouse
const category = this.warehouse.categories.find(cat => cat.id === categoryId);
if (category) {
return {
id: category.id,
name: category.name,
icon: category.icon || 'fas fa-box'
};
}
return categories.find(cat => cat.id === categoryId) ||
{ id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
return { id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
}
// Получение статуса запаса
@ -407,16 +469,16 @@ class CatalogManager {
}
}
// Просмотр товара
// Просмотр позиции
viewItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId);
if (!item) return;
// Здесь можно открыть модальное окно с подробной информацией
auth.showNotification(`Просмотр товара: ${item.name}`, 'info');
auth.showNotification(`Просмотр позиции: ${item.name}`, 'info');
}
// Редактирование товара
// Редактирование позиции
async editItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId);
if (!item) return;
@ -467,19 +529,19 @@ class CatalogManager {
this.currentItems[itemIndex] = { ...this.currentItems[itemIndex], ...updateData };
}
auth.showNotification('Товар успешно обновлен', 'success');
auth.showNotification('Позиция успешно обновлена', 'success');
// Закрываем модальное окно и обновляем каталог
$('#editItemModal').modal('hide');
this.applyFilters();
} catch (error) {
console.error('Ошибка при обновлении товара:', error);
auth.showNotification('Ошибка при обновлении товара', 'danger');
console.error('Ошибка при обновлении позиции:', error);
auth.showNotification('Ошибка при обновлении позиции', 'danger');
}
}
// Добавление нового товара
// Добавление новой позиции
async addNewItem() {
const formData = this.getFormData('#addItemForm');
@ -493,7 +555,7 @@ class CatalogManager {
// Добавляем в локальные данные
this.currentItems.push(newItem);
auth.showNotification('Товар успешно добавлен', 'success');
auth.showNotification('Позиция успешно добавлена', 'success');
// Закрываем модальное окно и обновляем каталог
$('#addItemModal').modal('hide');
@ -503,14 +565,14 @@ class CatalogManager {
$('#addItemForm')[0].reset();
} catch (error) {
console.error('Ошибка при добавлении товара:', error);
auth.showNotification('Ошибка при добавлении товара', 'danger');
console.error('Ошибка при добавлении позиции:', error);
auth.showNotification('Ошибка при добавлении позиции', 'danger');
}
}
// Удаление товара
// Удаление позиции
async deleteItem(itemId) {
if (!confirm('Вы уверены, что хотите удалить этот товар?')) {
if (!confirm('Вы уверены, что хотите удалить эту позицию?')) {
return;
}
@ -520,12 +582,12 @@ class CatalogManager {
// Удаляем из локальных данных
this.currentItems = this.currentItems.filter(i => i.id !== itemId);
auth.showNotification('Товар успешно удален', 'success');
auth.showNotification('Позиция успешно удалена', 'success');
this.applyFilters();
} catch (error) {
console.error('Ошибка при удалении товара:', error);
auth.showNotification('Ошибка при удалении товара', 'danger');
console.error('Ошибка при удалении позиции:', error);
auth.showNotification('Ошибка при удалении позиции', 'danger');
}
}
@ -632,6 +694,126 @@ class CatalogManager {
hideLoading() {
// Загрузка завершается в renderCatalog()
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
console.log('Обновление информации о пользователе в каталоге:', user);
if (user) {
// Обновляем имя пользователя в профиле
const profileToggle = $('#profileDropdown .dropdown-toggle');
if (profileToggle.length) {
profileToggle.html(`<i class="fas fa-user me-1"></i>${user.name}`);
}
// Показываем профиль для всех авторизованных пользователей
$('#profileDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
console.log('Показана панель администратора в каталоге');
} else {
$('#adminDropdown').hide();
console.log('Скрыта панель администратора в каталоге');
}
} else {
console.log('Пользователь не авторизован в каталоге');
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в навигацию
async loadCategoriesNav() {
try {
const categories = await this.warehouse.getCategories();
const categoriesNav = $('#categoriesNav');
// Очищаем существующие категории (кроме "Главная")
categoriesNav.find('li:not(:first-child)').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li class="nav-item">
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
categoriesNav.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий:', error);
}
}
// Обновление прав доступа
updatePermissions() {
const user = auth.getCurrentUser();
if (!user) return;
// Показываем кнопку добавления для администраторов и менеджеров
if (auth.hasRole('admin') || auth.hasRole('manager')) {
$('#addItemBtn').show();
} else {
$('#addItemBtn').hide();
}
// Показываем кнопку экспорта для всех авторизованных пользователей
$('#exportBtn').show();
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
// Обработка параметра категории из URL
handleCategoryFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
const categoryParam = urlParams.get('category');
if (categoryParam) {
// Устанавливаем фильтр категории
$('#categoryFilter').val(categoryParam);
// Применяем фильтры
this.applyFilters();
// Показываем уведомление о примененном фильтре
const categoryInfo = this.getCategoryInfo(categoryParam);
auth.showNotification(`Отфильтровано по категории: ${categoryInfo.name}`, 'info');
}
}
}
// Инициализация менеджера каталога

403
js/categories.js Normal file
View File

@ -0,0 +1,403 @@
// JavaScript для управления категориями
class CategoryManager {
constructor() {
this.warehouse = new WarehouseManager();
this.categories = [];
this.currentCategory = null;
this.init();
}
async init() {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = '../login.html';
return;
}
// Проверяем права доступа
if (!auth.hasRole('admin')) {
console.log('Пользователь не имеет прав администратора');
auth.showNotification('Недостаточно прав для доступа к этой странице', 'danger');
window.location.href = '../index.html';
return;
}
console.log('Инициализация управления категориями для администратора:', auth.getCurrentUser()?.name);
// Загружаем данные
await this.loadCategories();
this.updateStatistics();
this.renderCategoriesTable();
this.bindEvents();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
this.updateUserInfo();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
}
// Загрузка категорий
async loadCategories() {
this.categories = await this.warehouse.getCategories();
}
// Обновление статистики
updateStatistics() {
const totalCategories = this.categories.length;
const activeCategories = this.categories.filter(cat => cat.active).length;
const emptyCategories = this.categories.filter(cat => cat.itemCount === 0).length;
// Получаем общее количество позиций
const allItems = this.warehouse.getAllItemsSync();
const totalItems = allItems.length;
$('#totalCategories').text(totalCategories);
$('#totalItems').text(totalItems);
$('#activeCategories').text(activeCategories);
$('#emptyCategories').text(emptyCategories);
}
// Рендеринг таблицы категорий
renderCategoriesTable() {
const tbody = $('#categoriesTable tbody');
tbody.empty();
if (this.categories.length === 0) {
tbody.append(`
<tr>
<td colspan="7" class="text-center py-4">
<div class="empty-state">
<i class="fas fa-tags fa-3x text-muted mb-3"></i>
<h5>Категории не найдены</h5>
<p class="text-muted">Добавьте первую категорию для начала работы</p>
</div>
</td>
</tr>
`);
return;
}
this.categories.forEach(category => {
const row = this.createCategoryRow(category);
tbody.append(row);
});
}
// Создание строки категории
createCategoryRow(category) {
const statusBadge = category.active ?
'<span class="badge bg-success">Активна</span>' :
'<span class="badge bg-secondary">Неактивна</span>';
const iconPreview = category.icon ?
`<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>` :
'<i class="fas fa-tag text-muted"></i>';
return `
<tr data-category-id="${category.id}">
<td>${category.id}</td>
<td>
<div class="d-flex align-items-center">
<span class="me-2">${iconPreview}</span>
<strong>${category.name}</strong>
</div>
</td>
<td>${category.description || '-'}</td>
<td>
<code>${category.icon || 'fas fa-tag'}</code>
</td>
<td>
<span class="badge bg-info">${category.itemCount || 0}</span>
</td>
<td>${statusBadge}</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="categoryManager.editCategory('${category.id}')" title="Редактировать">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-outline-danger" onclick="categoryManager.deleteCategory('${category.id}')" title="Удалить">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
`;
}
// Привязка событий
bindEvents() {
// Добавление категории
$('#saveCategoryBtn').on('click', () => this.addCategory());
// Редактирование категории
$('#updateCategoryBtn').on('click', () => this.updateCategory());
// Очистка формы при закрытии модального окна
$('#addCategoryModal').on('hidden.bs.modal', () => this.clearAddForm());
$('#editCategoryModal').on('hidden.bs.modal', () => this.clearEditForm());
// Валидация формы
$('#addCategoryForm').on('submit', (e) => {
e.preventDefault();
this.addCategory();
});
$('#editCategoryForm').on('submit', (e) => {
e.preventDefault();
this.updateCategory();
});
}
// Добавление категории
async addCategory() {
try {
const formData = this.getAddFormData();
if (!formData.name.trim()) {
auth.showNotification('Введите название категории', 'warning');
return;
}
// Проверяем уникальность названия
const existingCategory = this.categories.find(cat =>
cat.name.toLowerCase() === formData.name.toLowerCase()
);
if (existingCategory) {
auth.showNotification('Категория с таким названием уже существует', 'warning');
return;
}
const newCategory = await this.warehouse.addCategory(formData);
this.categories.push(newCategory);
this.updateStatistics();
this.renderCategoriesTable();
$('#addCategoryModal').modal('hide');
auth.showNotification('Категория успешно добавлена', 'success');
} catch (error) {
console.error('Ошибка при добавлении категории:', error);
auth.showNotification('Ошибка при добавлении категории', 'danger');
}
}
// Редактирование категории
async editCategory(categoryId) {
try {
const category = this.categories.find(cat => cat.id === categoryId);
if (!category) {
auth.showNotification('Категория не найдена', 'danger');
return;
}
this.currentCategory = category;
this.fillEditForm(category);
$('#editCategoryModal').modal('show');
} catch (error) {
console.error('Ошибка при редактировании категории:', error);
auth.showNotification('Ошибка при загрузке категории', 'danger');
}
}
// Обновление категории
async updateCategory() {
try {
if (!this.currentCategory) {
auth.showNotification('Категория не выбрана', 'warning');
return;
}
const formData = this.getEditFormData();
if (!formData.name.trim()) {
auth.showNotification('Введите название категории', 'warning');
return;
}
// Проверяем уникальность названия (исключая текущую категорию)
const existingCategory = this.categories.find(cat =>
cat.id !== this.currentCategory.id &&
cat.name.toLowerCase() === formData.name.toLowerCase()
);
if (existingCategory) {
auth.showNotification('Категория с таким названием уже существует', 'warning');
return;
}
const updatedCategory = await this.warehouse.updateCategory(this.currentCategory.id, formData);
// Обновляем категорию в списке
const index = this.categories.findIndex(cat => cat.id === this.currentCategory.id);
if (index !== -1) {
this.categories[index] = updatedCategory;
}
this.updateStatistics();
this.renderCategoriesTable();
$('#editCategoryModal').modal('hide');
auth.showNotification('Категория успешно обновлена', 'success');
} catch (error) {
console.error('Ошибка при обновлении категории:', error);
auth.showNotification('Ошибка при обновлении категории', 'danger');
}
}
// Удаление категории
async deleteCategory(categoryId) {
try {
const category = this.categories.find(cat => cat.id === categoryId);
if (!category) {
auth.showNotification('Категория не найдена', 'danger');
return;
}
// Проверяем, есть ли позиции в этой категории
if (category.itemCount > 0) {
const confirmDelete = confirm(
`В категории "${category.name}" есть ${category.itemCount} позиций. ` +
'Удаление категории приведет к удалению всех позиций. Продолжить?'
);
if (!confirmDelete) {
return;
}
} else {
const confirmDelete = confirm(`Вы уверены, что хотите удалить категорию "${category.name}"?`);
if (!confirmDelete) {
return;
}
}
await this.warehouse.deleteCategory(categoryId);
// Удаляем категорию из списка
this.categories = this.categories.filter(cat => cat.id !== categoryId);
this.updateStatistics();
this.renderCategoriesTable();
auth.showNotification('Категория успешно удалена', 'success');
} catch (error) {
console.error('Ошибка при удалении категории:', error);
auth.showNotification('Ошибка при удалении категории', 'danger');
}
}
// Получение данных формы добавления
getAddFormData() {
return {
name: $('#categoryName').val().trim(),
description: $('#categoryDescription').val().trim(),
icon: $('#categoryIcon').val().trim() || 'fas fa-tag',
color: $('#categoryColor').val(),
active: $('#categoryActive').is(':checked')
};
}
// Получение данных формы редактирования
getEditFormData() {
return {
name: $('#editCategoryName').val().trim(),
description: $('#editCategoryDescription').val().trim(),
icon: $('#editCategoryIcon').val().trim() || 'fas fa-tag',
color: $('#editCategoryColor').val(),
active: $('#editCategoryActive').is(':checked')
};
}
// Заполнение формы редактирования
fillEditForm(category) {
$('#editCategoryId').val(category.id);
$('#editCategoryName').val(category.name);
$('#editCategoryDescription').val(category.description || '');
$('#editCategoryIcon').val(category.icon || 'fas fa-tag');
$('#editCategoryColor').val(category.color || '#2c5aa0');
$('#editCategoryActive').prop('checked', category.active);
}
// Очистка формы добавления
clearAddForm() {
$('#addCategoryForm')[0].reset();
$('#categoryColor').val('#2c5aa0');
$('#categoryActive').prop('checked', true);
}
// Очистка формы редактирования
clearEditForm() {
$('#editCategoryForm')[0].reset();
$('#editCategoryColor').val('#2c5aa0');
this.currentCategory = null;
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
if (user) {
$('#currentUserInfo').text(user.name);
$('#loginLink').hide();
$('#profileDropdown').show();
$('#adminDropdown').show();
} else {
$('#currentUserInfo').text('Гость');
$('#loginLink').show();
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="../catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
}
// Инициализация при загрузке страницы
let categoryManager;
$(document).ready(() => {
categoryManager = new CategoryManager();
// Обработка выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = '../login.html';
});
});

View File

@ -13,10 +13,39 @@ class HistoryManager {
}
init() {
$(document).ready(() => {
this.loadHistory();
this.bindEvents();
this.initializeFilters();
$(document).ready(async () => {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = 'login.html';
return;
}
console.log('Инициализация истории для пользователя:', auth.getCurrentUser()?.name);
// Загружаем историю операций
await this.loadHistory();
// Загружаем категории в навигацию
await this.loadCategoriesNav();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
// Обновляем информацию о пользователе
this.updateUserInfo();
// Привязываем события
this.bindEvents();
// Инициализируем фильтры
this.initializeFilters();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
});
}
@ -32,6 +61,13 @@ class HistoryManager {
$('#exportBtn').on('click', () => this.exportOperations());
$('#generateReportBtn').on('click', () => this.generateReport());
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
// Установка дат по умолчанию
this.setDefaultDates();
}
@ -44,7 +80,7 @@ class HistoryManager {
// Загружаем операции
this.operations = await this.warehouse.getOperations();
// Загружаем товары для фильтра
// Загружаем позиции для фильтра
const items = await this.warehouse.getAllItems();
this.populateItemFilter(items);
@ -89,7 +125,7 @@ class HistoryManager {
$('#reportEndDate').val(today.toISOString().split('T')[0]);
}
// Заполнение фильтра товаров
// Заполнение фильтра позиций
populateItemFilter(items) {
const itemFilter = $('#itemFilter');
const reportItem = $('#reportItem');
@ -147,7 +183,7 @@ class HistoryManager {
}
}
// Фильтр по товару
// Фильтр по позиции
if (this.filters.itemId && operation.itemId !== parseInt(this.filters.itemId)) {
return false;
}
@ -405,10 +441,10 @@ class HistoryManager {
</table>
</div>
<div class="col-md-6">
<h6>Детали товара</h6>
<h6>Детали позиции</h6>
<table class="table table-sm">
<tr>
<td><strong>Товар:</strong></td>
<td><strong>Позиция:</strong></td>
<td>${operation.itemName}</td>
</tr>
<tr>
@ -614,6 +650,78 @@ class HistoryManager {
hideLoading() {
// Загрузка завершается в renderOperations()
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
if (user) {
$('#currentUserInfo').text(user.name);
$('#loginLink').hide();
$('#profileDropdown').show();
$('#adminDropdown').show();
} else {
$('#currentUserInfo').text('Гость');
$('#loginLink').show();
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в навигацию
async loadCategoriesNav() {
try {
const categories = await this.warehouse.getCategories();
const categoriesNav = $('#categoriesNav');
// Очищаем существующие категории (кроме "Главная")
categoriesNav.find('li:not(:first-child)').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li class="nav-item">
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
categoriesNav.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий:', error);
}
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
}
// Инициализация менеджера истории

View File

@ -8,10 +8,44 @@ class MainApp {
init() {
// Инициализация после загрузки DOM
$(document).ready(() => {
this.loadDashboardData();
this.bindEvents();
this.initializeModals();
$(document).ready(async () => {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = 'login.html';
return;
}
console.log('Инициализация главной страницы для пользователя:', auth.getCurrentUser()?.name);
// Загружаем данные дашборда
await this.loadDashboardData();
// Загружаем категории в навигацию
await this.loadCategoriesNav();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
// Обновляем информацию о пользователе
this.updateUserInfo();
// Принудительно обновляем еще раз через небольшую задержку
setTimeout(() => {
this.updateUserInfo();
}, 500);
// Привязываем события
this.bindEvents();
// Инициализируем модальные окна
this.initializeModals();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
});
}
@ -29,6 +63,13 @@ class MainApp {
// Обработчики для экспорта
$('#exportBtn').on('click', () => this.exportData());
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
}
// Загрузка данных для дашборда
@ -38,14 +79,14 @@ class MainApp {
this.showLoading();
// Загружаем статистику
const stats = await this.warehouse.getStatistics();
const stats = this.warehouse.getStatistics();
this.updateStatistics(stats);
// Загружаем последние операции
const operations = await this.warehouse.getRecentOperations(10);
this.updateOperationsTable(operations);
// Загружаем товары для модальных окон
// Загружаем позиции для модальных окон
const items = await this.warehouse.getAllItems();
this.populateItemSelects(items);
@ -146,7 +187,7 @@ class MainApp {
});
}
// Заполнение селектов товаров
// Заполнение селектов позиций
populateItemSelects(items) {
const selects = ['#incomingItem', '#outgoingItem'];
@ -160,7 +201,7 @@ class MainApp {
});
}
// Добавление нового товара
// Добавление новой позиции
async addNewItem() {
const formData = this.getFormData('#addItemForm');
@ -170,7 +211,7 @@ class MainApp {
try {
const newItem = await this.warehouse.addItem(formData);
auth.showNotification('Товар успешно добавлен', 'success');
auth.showNotification('Позиция успешно добавлена', 'success');
// Закрываем модальное окно
$('#addItemModal').modal('hide');
@ -182,8 +223,8 @@ class MainApp {
$('#addItemForm')[0].reset();
} catch (error) {
console.error('Ошибка при добавлении товара:', error);
auth.showNotification('Ошибка при добавлении товара', 'danger');
console.error('Ошибка при добавлении позиции:', error);
auth.showNotification('Ошибка при добавлении позиции', 'danger');
}
}
@ -424,6 +465,92 @@ class MainApp {
hideLoading() {
$('#loadingOverlay').remove();
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
console.log('Обновление информации о пользователе:', user);
if (user) {
// Обновляем имя пользователя в профиле
const profileToggle = $('#profileDropdown .dropdown-toggle');
if (profileToggle.length) {
profileToggle.html(`<i class="fas fa-user me-1"></i>${user.name}`);
}
// Показываем профиль для всех авторизованных пользователей
$('#profileDropdown').show();
// Показываем админ панель только для администраторов
if (auth.hasRole('admin')) {
$('#adminDropdown').show();
console.log('Показана панель администратора');
} else {
$('#adminDropdown').hide();
console.log('Скрыта панель администратора');
}
} else {
console.log('Пользователь не авторизован');
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в навигацию
async loadCategoriesNav() {
try {
const categories = await this.warehouse.getCategories();
const categoriesNav = $('#categoriesNav');
// Очищаем существующие категории (кроме "Главная")
categoriesNav.find('li:not(:first-child)').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li class="nav-item">
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
categoriesNav.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий:', error);
}
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
}
// Инициализация приложения

View File

@ -10,9 +10,36 @@ class ProfileManager {
}
init() {
$(document).ready(() => {
this.loadProfile();
this.bindEvents();
$(document).ready(async () => {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = 'login.html';
return;
}
console.log('Инициализация профиля для пользователя:', auth.getCurrentUser()?.name);
// Загружаем профиль
await this.loadProfile();
// Загружаем категории в навигацию
await this.loadCategoriesNav();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
// Обновляем информацию о пользователе
this.updateUserInfo();
// Привязываем события
this.bindEvents();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
});
}
@ -30,6 +57,13 @@ class ProfileManager {
e.preventDefault();
this.saveProfile();
});
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
}
// Загрузка профиля
@ -427,6 +461,78 @@ class ProfileManager {
return recommendations;
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
if (user) {
$('#currentUserInfo').text(user.name);
$('#loginLink').hide();
$('#profileDropdown').show();
$('#adminDropdown').show();
} else {
$('#currentUserInfo').text('Гость');
$('#loginLink').show();
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в навигацию
async loadCategoriesNav() {
try {
const categories = await this.warehouse.getCategories();
const categoriesNav = $('#categoriesNav');
// Очищаем существующие категории (кроме "Главная")
categoriesNav.find('li:not(:first-child)').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li class="nav-item">
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
categoriesNav.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий:', error);
}
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
}
// Инициализация менеджера профиля

View File

@ -16,10 +16,39 @@ class SearchManager {
}
init() {
$(document).ready(() => {
this.loadCategories();
this.bindEvents();
this.initializeFilters();
$(document).ready(async () => {
try {
// Проверяем авторизацию
if (!auth.isUserAuthenticated()) {
console.log('Пользователь не авторизован, перенаправление на login.html');
window.location.href = 'login.html';
return;
}
console.log('Инициализация поиска для пользователя:', auth.getCurrentUser()?.name);
// Загружаем категории
await this.loadCategories();
// Загружаем категории в навигацию
await this.loadCategoriesNav();
// Загружаем категории в выпадающее меню
await this.loadCategoriesDropdown();
// Обновляем информацию о пользователе
this.updateUserInfo();
// Привязываем события
this.bindEvents();
// Инициализируем фильтры
this.initializeFilters();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
});
}
@ -45,6 +74,13 @@ class SearchManager {
e.preventDefault();
$(e.target).tab('show');
});
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
}
// Загрузка категорий для фильтра
@ -113,7 +149,7 @@ class SearchManager {
this.filters.endDate = $('#endDate').val();
}
// Поиск товаров
// Поиск позиций
async searchItems(query) {
if (this.filters.searchType === 'operations') {
this.searchResults.items = [];
@ -218,7 +254,7 @@ class SearchManager {
<div class="col-12">
<div class="empty-state">
<i class="fas fa-box-open"></i>
<h5>Товары не найдены</h5>
<h5>Позиции не найдены</h5>
<p>Попробуйте изменить поисковый запрос</p>
</div>
</div>
@ -226,7 +262,7 @@ class SearchManager {
return;
}
// Пагинация для товаров
// Пагинация для позиций
const totalPages = Math.ceil(this.searchResults.items.length / this.resultsPerPage);
const startIndex = (this.currentPage - 1) * this.resultsPerPage;
const endIndex = startIndex + this.resultsPerPage;
@ -238,7 +274,7 @@ class SearchManager {
});
}
// Создание карточки товара
// Создание карточки позиции
createItemCard(item) {
const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item);
@ -404,7 +440,7 @@ class SearchManager {
return;
}
// Сортировка товаров
// Сортировка позиций
this.searchResults.items.sort((a, b) => {
let aValue = a[sortBy];
let bValue = b[sortBy];
@ -475,7 +511,7 @@ class SearchManager {
csv += `Запрос: ${this.currentQuery}\n`;
csv += `Дата поиска: ${new Date().toLocaleDateString('ru-RU')}\n\n`;
// Экспорт товаров
// Экспорт позиций
if (this.searchResults.items.length > 0) {
csv += 'ТОВАРЫ\n';
csv += 'ID,Название,Категория,Цена,Количество,Описание\n';
@ -513,7 +549,7 @@ class SearchManager {
}
}
// Просмотр деталей товара
// Просмотр деталей позиции
async viewItemDetails(itemId) {
try {
const item = await this.warehouse.getItemById(itemId);
@ -587,8 +623,8 @@ class SearchManager {
$('#itemDetailsModal').modal('show');
} catch (error) {
console.error('Ошибка при загрузке деталей товара:', error);
auth.showNotification('Ошибка при загрузке деталей товара', 'danger');
console.error('Ошибка при загрузке деталей позиции:', error);
auth.showNotification('Ошибка при загрузке деталей позиции', 'danger');
}
}
@ -661,6 +697,78 @@ class SearchManager {
hideLoading() {
$('#loadingState').hide();
}
// Обновление информации о пользователе
updateUserInfo() {
const user = auth.getCurrentUser();
if (user) {
$('#currentUserInfo').text(user.name);
$('#loginLink').hide();
$('#profileDropdown').show();
$('#adminDropdown').show();
} else {
$('#currentUserInfo').text('Гость');
$('#loginLink').show();
$('#profileDropdown').hide();
$('#adminDropdown').hide();
}
}
// Загрузка категорий в навигацию
async loadCategoriesNav() {
try {
const categories = await this.warehouse.getCategories();
const categoriesNav = $('#categoriesNav');
// Очищаем существующие категории (кроме "Главная")
categoriesNav.find('li:not(:first-child)').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li class="nav-item">
<a class="nav-link" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
categoriesNav.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий:', error);
}
}
// Загрузка категорий в выпадающее меню
async loadCategoriesDropdown() {
try {
const categories = await this.warehouse.getCategories();
const dropdown = $('#catalogCategoriesDropdown');
// Очищаем существующие категории (кроме "Все позиции" и разделителя)
dropdown.find('li:not(:first-child):not(:nth-child(2))').remove();
// Добавляем активные категории
categories.forEach(category => {
if (category.active) {
const categoryLink = `
<li>
<a class="dropdown-item" href="catalog.html?category=${category.id}" title="${category.description || ''}">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
${category.name}
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
console.error('Ошибка загрузки категорий в выпадающее меню:', error);
}
}
}
// Инициализация менеджера поиска

View File

@ -7,7 +7,7 @@ class WarehouseManager {
this.categories = this.loadCategories();
}
// Загрузка товаров из localStorage
// Загрузка позиций из localStorage
loadItems() {
const items = localStorage.getItem('warehouse_items');
return items ? JSON.parse(items) : this.getDefaultItems();
@ -21,16 +21,88 @@ class WarehouseManager {
// Загрузка категорий
loadCategories() {
const categories = localStorage.getItem('warehouse_categories');
if (categories) {
this.categories = JSON.parse(categories);
} else {
// Загружаем категории по умолчанию
this.categories = this.getDefaultCategories();
this.saveCategories();
}
// Обновляем количество позиций в каждой категории
this.updateCategoryItemCounts();
}
// Получение категорий по умолчанию
getDefaultCategories() {
return [
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' },
{ id: 'clothing', name: 'Одежда', icon: 'fas fa-tshirt' },
{ id: 'tools', name: 'Инструменты', icon: 'fas fa-tools' },
{ id: 'office', name: 'Офисные принадлежности', icon: 'fas fa-briefcase' },
{ id: 'furniture', name: 'Мебель', icon: 'fas fa-couch' },
{ id: 'books', name: 'Книги', icon: 'fas fa-book' }
{
id: 'electronics',
name: 'Электроника',
description: 'Электронные компоненты и устройства',
icon: 'fas fa-microchip',
color: '#2c5aa0',
active: true,
itemCount: 0,
createdAt: new Date().toISOString()
},
{
id: 'components',
name: 'Компоненты',
description: 'Радиодетали и электронные компоненты',
icon: 'fas fa-cogs',
color: '#28a745',
active: true,
itemCount: 0,
createdAt: new Date().toISOString()
},
{
id: 'tools',
name: 'Инструменты',
description: 'Ручной и электроинструмент',
icon: 'fas fa-tools',
color: '#ffc107',
active: true,
itemCount: 0,
createdAt: new Date().toISOString()
},
{
id: 'office',
name: 'Офисные принадлежности',
description: 'Канцелярские товары и офисное оборудование',
icon: 'fas fa-briefcase',
color: '#6c757d',
active: true,
itemCount: 0,
createdAt: new Date().toISOString()
},
{
id: 'furniture',
name: 'Мебель',
description: 'Офисная мебель и оборудование',
icon: 'fas fa-chair',
color: '#8b4513',
active: true,
itemCount: 0,
createdAt: new Date().toISOString()
}
];
}
// Сохранение категорий
saveCategories() {
localStorage.setItem('warehouse_categories', JSON.stringify(this.categories));
}
// Обновление количества позиций в категориях
updateCategoryItemCounts() {
this.categories.forEach(category => {
category.itemCount = this.items.filter(item => item.category === category.id).length;
});
this.saveCategories();
}
// Сохранение данных в localStorage
saveItems() {
localStorage.setItem('warehouse_items', JSON.stringify(this.items));
@ -40,7 +112,7 @@ class WarehouseManager {
localStorage.setItem('warehouse_operations', JSON.stringify(this.operations));
}
// Получение товаров по умолчанию
// Получение позиций по умолчанию
getDefaultItems() {
return [
{
@ -54,8 +126,8 @@ class WarehouseManager {
minQuantity: 5,
location: 'Стеллаж A-1',
supplier: 'Dell Inc.',
createdAt: '2024-01-15T10:00:00Z',
updatedAt: '2024-01-15T10:00:00Z'
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z'
},
{
id: 2,
@ -68,13 +140,13 @@ class WarehouseManager {
minQuantity: 3,
location: 'Стеллаж A-2',
supplier: 'HP Inc.',
createdAt: '2024-01-10T14:30:00Z',
updatedAt: '2024-01-10T14:30:00Z'
createdAt: '2025-01-10T14:30:00Z',
updatedAt: '2025-01-10T14:30:00Z'
},
{
id: 3,
name: 'Офисный стул',
category: 'furniture',
category: 'office',
price: 3500,
quantity: 25,
description: 'Офисный стул с подлокотниками, черный',
@ -82,8 +154,8 @@ class WarehouseManager {
minQuantity: 10,
location: 'Стеллаж B-1',
supplier: 'МебельСтрой',
createdAt: '2024-01-05T09:15:00Z',
updatedAt: '2024-01-05T09:15:00Z'
createdAt: '2025-01-05T09:15:00Z',
updatedAt: '2025-01-05T09:15:00Z'
},
{
id: 4,
@ -96,8 +168,8 @@ class WarehouseManager {
minQuantity: 20,
location: 'Стеллаж C-1',
supplier: 'ОфисМаркет',
createdAt: '2024-01-12T11:45:00Z',
updatedAt: '2024-01-12T11:45:00Z'
createdAt: '2025-01-12T11:45:00Z',
updatedAt: '2025-01-12T11:45:00Z'
},
{
id: 5,
@ -110,14 +182,14 @@ class WarehouseManager {
minQuantity: 15,
location: 'Стеллаж D-1',
supplier: 'ИнструментСтрой',
createdAt: '2024-01-08T16:20:00Z',
updatedAt: '2024-01-08T16:20:00Z'
createdAt: '2025-01-08T16:20:00Z',
updatedAt: '2025-01-08T16:20:00Z'
}
];
}
// Получение статистики
async getStatistics() {
getStatistics() {
const today = new Date().toISOString().split('T')[0];
const totalItems = this.items.reduce((sum, item) => sum + item.quantity, 0);
@ -137,17 +209,22 @@ class WarehouseManager {
};
}
// Получение всех товаров
// Получение всех позиций
async getAllItems() {
return this.items;
}
// Получение товара по ID
// Синхронное получение всех позиций
getAllItemsSync() {
return this.items;
}
// Получение позиции по ID
async getItemById(id) {
return this.items.find(item => item.id === parseInt(id));
}
// Добавление нового товара
// Добавление новой позиции
async addItem(itemData) {
const newItem = {
id: this.getNextItemId(),
@ -167,22 +244,22 @@ class WarehouseManager {
this.items.push(newItem);
this.saveItems();
// Создаем операцию прихода для нового товара
// Создаем операцию прихода для новой позиции
await this.createOperation({
type: 'incoming',
itemId: newItem.id,
quantity: newItem.quantity,
notes: 'Добавление нового товара'
notes: 'Добавление новой позиции'
});
return newItem;
}
// Обновление товара
// Обновление позиции
async updateItem(id, itemData) {
const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
if (itemIndex === -1) {
throw new Error('Товар не найден');
throw new Error('Позиция не найдена');
}
this.items[itemIndex] = {
@ -195,11 +272,11 @@ class WarehouseManager {
return this.items[itemIndex];
}
// Удаление товара
// Удаление позиции
async deleteItem(id) {
const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
if (itemIndex === -1) {
throw new Error('Товар не найден');
throw new Error('Позиция не найдена');
}
this.items.splice(itemIndex, 1);
@ -214,10 +291,10 @@ class WarehouseManager {
const item = await this.getItemById(itemId);
if (!item) {
throw new Error('Товар не найден');
throw new Error('Позиция не найдена');
}
// Обновляем количество товара
// Обновляем количество позиции
item.quantity += quantity;
item.updatedAt = new Date().toISOString();
this.saveItems();
@ -241,15 +318,15 @@ class WarehouseManager {
const item = await this.getItemById(itemId);
if (!item) {
throw new Error('Товар не найден');
throw new Error('Позиция не найдена');
}
// Проверяем достаточность товара
// Проверяем достаточность позиции
if (item.quantity < quantity) {
throw new Error(`Недостаточно товара. Доступно: ${item.quantity} шт.`);
throw new Error(`Недостаточно позиций. Доступно: ${item.quantity} шт.`);
}
// Обновляем количество товара
// Обновляем количество позиции
item.quantity -= quantity;
item.updatedAt = new Date().toISOString();
this.saveItems();
@ -401,18 +478,63 @@ class WarehouseManager {
return this.categories;
}
// Получение категории по ID
async getCategoryById(id) {
return this.categories.find(cat => cat.id === id);
}
// Добавление категории
async addCategory(categoryData) {
const newCategory = {
id: categoryData.id || this.generateCategoryId(),
name: categoryData.name,
icon: categoryData.icon || 'fas fa-box'
id: this.generateCategoryId(),
...categoryData,
itemCount: 0,
createdAt: new Date().toISOString()
};
this.categories.push(newCategory);
this.saveCategories();
return newCategory;
}
// Обновление категории
async updateCategory(id, categoryData) {
const categoryIndex = this.categories.findIndex(cat => cat.id === id);
if (categoryIndex === -1) {
throw new Error('Категория не найдена');
}
this.categories[categoryIndex] = {
...this.categories[categoryIndex],
...categoryData,
updatedAt: new Date().toISOString()
};
this.saveCategories();
return this.categories[categoryIndex];
}
// Удаление категории
async deleteCategory(id) {
const categoryIndex = this.categories.findIndex(cat => cat.id === id);
if (categoryIndex === -1) {
throw new Error('Категория не найдена');
}
// Удаляем все позиции этой категории
this.items = this.items.filter(item => item.category !== id);
this.saveItems();
// Удаляем категорию
this.categories.splice(categoryIndex, 1);
this.saveCategories();
// Обновляем количество позиций в остальных категориях
this.updateCategoryItemCounts();
return true;
}
// Генерация следующего ID для товара
getNextItemId() {
const maxId = Math.max(...this.items.map(item => item.id), 0);

View File

@ -8,8 +8,8 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div class="login-container">
<body class="d-flex flex-column min-vh-100">
<div class="login-container flex-grow-1">
<div class="login-card">
<div class="login-header">
<i class="fas fa-warehouse fa-3x mb-3"></i>
@ -17,22 +17,30 @@
<p class="mb-0">Система управления складом</p>
</div>
<div class="login-body">
<form id="loginForm">
<form id="loginForm" novalidate>
<div class="mb-3">
<label for="username" class="form-label">
<i class="fas fa-user me-2"></i>Имя пользователя
</label>
<input type="text" class="form-control" id="username" required>
<input type="text" class="form-control" id="username" required
placeholder="Введите имя пользователя" autocomplete="username">
<div class="invalid-feedback" id="usernameError"></div>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Пароль
</label>
<input type="password" class="form-control" id="password" required>
<input type="password" class="form-control" id="password" required
placeholder="Введите пароль" autocomplete="current-password">
<div class="invalid-feedback" id="passwordError"></div>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">
<button type="submit" class="btn btn-primary w-100 mb-3" id="loginBtn">
<i class="fas fa-sign-in-alt me-2"></i>Войти в систему
</button>
<div id="loginLoading" class="text-center" style="display: none;">
<div class="loading-spinner text-primary mb-2"></div>
<small class="text-muted">Выполняется вход...</small>
</div>
</form>
<!-- Информация о тестовых пользователях -->
@ -100,9 +108,77 @@
</div>
</div>
<!-- Footer -->
<footer class="bg-dark text-white py-3 mt-auto">
<div class="container">
<div class="row align-items-center">
<div class="col-md-6">
<small>
<i class="fas fa-shield-alt me-2"></i>Безопасная система авторизации
</small>
</div>
<div class="col-md-6 text-md-end">
<p class="mb-0">&copy; 2025 Склад-Менеджер. Все права защищены.</p>
</div>
</div>
</div>
</footer>
<!-- Скрипты -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="js/auth.js"></script>
<!-- Дополнительный JavaScript для улучшения UX -->
<script>
$(document).ready(function() {
// Обработка формы входа с улучшенной валидацией
$('#loginForm').on('submit', function(e) {
e.preventDefault();
// Сброс ошибок
$('.form-control').removeClass('is-invalid');
$('.invalid-feedback').hide();
const username = $('#username').val().trim();
const password = $('#password').val().trim();
let hasErrors = false;
// Валидация имени пользователя
if (!username) {
$('#username').addClass('is-invalid');
$('#usernameError').text('Введите имя пользователя').show();
hasErrors = true;
}
// Валидация пароля
if (!password) {
$('#password').addClass('is-invalid');
$('#passwordError').text('Введите пароль').show();
hasErrors = true;
}
if (hasErrors) {
return;
}
// Показываем индикатор загрузки
$('#loginBtn').prop('disabled', true);
$('#loginLoading').show();
// Вызываем метод входа из auth.js
auth.login();
});
// Убираем ошибки при вводе
$('.form-control').on('input', function() {
$(this).removeClass('is-invalid');
$(this).siblings('.invalid-feedback').hide();
});
// Автофокус на поле имени пользователя
$('#username').focus();
});
</script>
</body>
</html>

View File

@ -9,51 +9,63 @@
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- Навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="index.html">
<i class="fas fa-warehouse me-2"></i>
Склад-Менеджер
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="catalog.html">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="history.html">
<i class="fas fa-history me-1"></i>История
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="search.html">
<i class="fas fa-search me-1"></i>Поиск
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown">
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
@ -68,6 +80,24 @@
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Заголовок профиля -->
<div class="profile-header">
<div class="container">
@ -397,4 +427,4 @@
<script src="js/warehouse.js"></script>
<script src="js/profile.js"></script>
</body>
</html>
</html>

View File

@ -9,51 +9,63 @@
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<!-- Навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- Главная навигационная панель -->
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" href="index.html">
<i class="fas fa-warehouse me-2"></i>
Склад-Менеджер
<i class="fas fa-warehouse me-2 text-primary"></i>
<span class="fw-bold">Склад-Менеджер</span>
</a>
<!-- Поиск в шапке -->
<div class="navbar-search me-auto ms-4">
<div class="input-group" style="width: 400px;">
<input type="text" class="form-control" placeholder="Поиск позиций..." id="headerSearch">
<button class="btn btn-primary" type="button">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="catalog.html">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог
</a>
<ul class="dropdown-menu" id="catalogCategoriesDropdown">
<li><a class="dropdown-item" href="catalog.html">Все позиции</a></li>
<li><hr class="dropdown-divider"></li>
<!-- Категории будут загружены динамически -->
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="history.html">
<i class="fas fa-history me-1"></i>История
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="search.html">
<i class="fas fa-search me-1"></i>Поиск
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории товаров</a></li>
<li><a class="dropdown-item" href="admin/categories.html">Категории позиций</a></li>
<li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="profileDropdown">
<li class="nav-item dropdown" id="profileDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль
</a>
@ -68,6 +80,24 @@
</div>
</nav>
<!-- Панель категорий -->
<div class="categories-bar bg-light border-bottom">
<div class="container">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills categories-nav" id="categoriesNav">
<li class="nav-item">
<a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная
</a>
</li>
<!-- Категории будут загружены динамически -->
</ul>
</div>
</div>
</div>
</div>
<!-- Основной контент -->
<main class="container mt-4">
<!-- Заголовок страницы -->
@ -176,7 +206,7 @@
<ul class="nav nav-tabs mb-3" id="resultsTabs">
<li class="nav-item">
<a class="nav-link active" id="items-tab" data-bs-toggle="tab" href="#itemsResults">
<i class="fas fa-boxes me-1"></i>Товары <span class="badge bg-secondary" id="itemsCount">0</span>
<i class="fas fa-boxes me-1"></i>Позиции <span class="badge bg-secondary" id="itemsCount">0</span>
</a>
</li>
<li class="nav-item">
@ -232,8 +262,8 @@
<!-- Пустое состояние -->
<div id="emptyState" class="text-center py-5">
<i class="fas fa-search fa-3x text-muted mb-3"></i>
<h4>Начните поиск</h4>
<p class="text-muted">Введите запрос в поле поиска выше, чтобы найти товары или операции</p>
<h4>Начните поиск</h4>
<p class="text-muted">Введите запрос в поле поиска выше, чтобы найти позиции или операции</p>
</div>
<!-- Состояние загрузки -->
@ -248,10 +278,10 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-box me-2"></i>
Детали товара
</h5>
<h5 class="modal-title">
<i class="fas fa-box me-2"></i>
Детали позиции
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="itemDetailsContent">
@ -289,4 +319,4 @@
<script src="js/warehouse.js"></script>
<script src="js/search.js"></script>
</body>
</html>
</html>