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 форматах - **Экспорт данных**: выгрузка в CSV и JSON форматах
### Система безопасности
- **Многоуровневая авторизация** с разными ролями
- **Контроль доступа** к функциям системы
- **Безопасные сессии** пользователей
- **Валидация данных** на всех уровнях
- **Проверка прав доступа** для каждой операции
- **Безопасная обработка ошибок** с информативными уведомлениями
- **URL валидация** параметров и фильтров
### Уровни доступа ### Уровни доступа
#### 🔧 Администратор (admin/admin123) #### 🔧 Администратор (admin/admin123)
@ -53,10 +62,11 @@ warehouse-system/
│ ├── auth.js # Система авторизации │ ├── auth.js # Система авторизации
│ ├── main.js # Основная логика │ ├── main.js # Основная логика
│ ├── warehouse.js # Управление складом │ ├── warehouse.js # Управление складом
│ ├── catalog.js # Каталог товаров │ ├── catalog.js # Каталог товаров (с проверкой прав доступа)
│ ├── history.js # История операций │ ├── history.js # История операций
│ ├── search.js # Поиск │ ├── search.js # Поиск
── profile.js # Профиль пользователя ── profile.js # Профиль пользователя
│ └── categories.js # Управление категориями
└── README.md # Документация └── 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` 1. Откройте `login.html`
2. Используйте тестовые учетные записи: 2. Используйте тестовые учетные записи:
- **Администратор**: `admin` / `admin123` - **Администратор**: `admin` / `admin123`
@ -80,7 +105,7 @@ warehouse-system/
- **Оператор**: `operator` / `operator123` - **Оператор**: `operator` / `operator123`
- **Наблюдатель**: `viewer` / `viewer123` - **Наблюдатель**: `viewer` / `viewer123`
### 2. Начальная настройка ### 3. Начальная настройка
1. Войдите как администратор 1. Войдите как администратор
2. Добавьте первые товары через главную страницу 2. Добавьте первые товары через главную страницу
3. Настройте категории товаров 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. Создавайте, редактируйте, удаляйте пользователей 4. Создавайте, редактируйте, удаляйте пользователей
### Настройка категорий ### Настройка категорий
1. Откройте "Категории товаров" 1. Откройте "Категории позиций"
2. Добавьте нужные категории 2. Добавьте нужные категории
3. Настройте иконки и описания 3. Настройте иконки и описания
4. Категории автоматически появятся в выпадающем меню
5. Изменения применяются мгновенно на всех страницах
### Генерация отчетов ### Генерация отчетов
1. Выберите тип отчета 1. Выберите тип отчета
@ -138,7 +178,7 @@ warehouse-system/
## 📊 Структура данных ## 📊 Структура данных
### Товар ### Позиция
```javascript ```javascript
{ {
id: 1, id: 1,
@ -151,8 +191,8 @@ warehouse-system/
minQuantity: 10, minQuantity: 10,
location: "Стеллаж A-1", location: "Стеллаж A-1",
supplier: "Поставщик", supplier: "Поставщик",
createdAt: "2024-01-15T10:00:00Z", createdAt: "2025-01-15T10:00:00Z",
updatedAt: "2024-01-15T10:00:00Z" updatedAt: "2025-01-15T10:00:00Z"
} }
``` ```
@ -166,7 +206,7 @@ warehouse-system/
quantity: 10, quantity: 10,
employeeId: 1, employeeId: 1,
employeeName: "Имя сотрудника", employeeName: "Имя сотрудника",
date: "2024-01-15T10:00:00Z", date: "2025-01-15T10:00:00Z",
status: "completed", status: "completed",
supplier: "Поставщик", supplier: "Поставщик",
recipient: "Получатель", 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', id: 'new-category',
name: 'Новая категория', 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 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
- Убедитесь, что JavaScript включен в браузере
#### Не сохраняются данные #### Не сохраняются данные
- Проверьте доступ к LocalStorage - Проверьте доступ к LocalStorage
@ -251,6 +364,14 @@ sudo certbot --nginx -d your-domain.com
- Очистите кэш браузера - Очистите кэш браузера
- Проверьте количество товаров/операций - Проверьте количество товаров/операций
#### Проблемы с каталогом
- Проверьте права доступа пользователя
- Убедитесь, что категории загружены корректно
- Проверьте консоль на ошибки загрузки данных
- Очистите фильтры, если каталог пустой
- Проверьте URL параметры для фильтрации по категориям
- Убедитесь, что выпадающее меню категорий загружается
### Логи и отладка ### Логи и отладка
Откройте консоль разработчика (F12) для просмотра ошибок и логов. Откройте консоль разработчика (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 - Email: support@warehouse-system.com
- Телефон: +7 (977) 665-45-78 - Телефон: +7 (XXX) XXX-XX-XX
- Telegram: @dark7es - Веб-сайт: https://warehouse-system.com
- Discord: @dark7es
---
**Версия**: 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"> <link href="css/style.css" rel="stylesheet">
</head> </head>
<body> <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"> <div class="container">
<a class="navbar-brand" href="index.html"> <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> </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"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="index.html"> <a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная <i class="fas fa-home me-1"></i>Главная
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link active" href="catalog.html"> <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>Каталог <i class="fas fa-boxes me-1"></i>Каталог
</a> </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>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="history.html"> <a class="nav-link" href="history.html">
@ -41,19 +60,17 @@
<i class="fas fa-search me-1"></i>Поиск <i class="fas fa-search me-1"></i>Поиск
</a> </a>
</li> </li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown" id="adminDropdown" style="display: none;"> <li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование <i class="fas fa-cog me-1"></i>Администрирование
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li> <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> <li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul> </ul>
</li> </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"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль <i class="fas fa-user me-1"></i>Профиль
</a> </a>
@ -68,6 +85,24 @@
</div> </div>
</nav> </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"> <main class="container mt-4">
<!-- Заголовок страницы --> <!-- Заголовок страницы -->
@ -77,16 +112,17 @@
<div> <div>
<h1 class="mb-1"> <h1 class="mb-1">
<i class="fas fa-boxes me-2"></i> <i class="fas fa-boxes me-2"></i>
Каталог товаров Каталог позиций
</h1> </h1>
<p class="text-muted mb-0">Управление товарными позициями склада</p> <p class="text-muted mb-0">Управление позициями склада</p>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button class="btn btn-outline-primary" id="exportBtn"> <button class="btn btn-outline-primary" id="exportBtn">
<i class="fas fa-download me-1"></i>Экспорт <i class="fas fa-download me-1"></i>Экспорт
</button> </button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn"> <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal" id="addItemBtn"
<i class="fas fa-plus me-1"></i>Добавить товар style="display: none;" title="Требуются права администратора или менеджера">
<i class="fas fa-plus me-1"></i>Добавить позицию
</button> </button>
</div> </div>
</div> </div>
@ -97,7 +133,7 @@
<div class="catalog-filters"> <div class="catalog-filters">
<div class="row"> <div class="row">
<div class="col-md-4 mb-3"> <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"> <div class="input-group">
<span class="input-group-text"> <span class="input-group-text">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
@ -154,7 +190,7 @@
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div> <div>
<h4 class="card-title" id="totalItemsCount">0</h4> <h4 class="card-title" id="totalItemsCount">0</h4>
<p class="card-text">Всего товаров</p> <p class="card-text">Всего позиций</p>
</div> </div>
<i class="fas fa-boxes fa-2x"></i> <i class="fas fa-boxes fa-2x"></i>
</div> </div>

View File

@ -1,17 +1,20 @@
/* Основные стили для системы управления складом */ /* Основные стили для системы управления складом */
:root { :root {
--primary-color: #0d6efd; --primary-color: #2c5aa0;
--secondary-color: #6c757d; --secondary-color: #6c757d;
--success-color: #198754; --success-color: #28a745;
--warning-color: #ffc107; --warning-color: #ffc107;
--danger-color: #dc3545; --danger-color: #dc3545;
--info-color: #0dcaf0; --info-color: #17a2b8;
--light-color: #f8f9fa; --light-color: #f8f9fa;
--dark-color: #212529; --dark-color: #343a40;
--border-radius: 0.375rem; --border-radius: 0.25rem;
--box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); --box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
--transition: all 0.15s ease-in-out; --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; 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 { .navbar-brand {
font-weight: 700; font-weight: 700;
font-size: 1.5rem; 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 { .navbar-nav .nav-link {
font-weight: 500; font-weight: 500;
transition: var(--transition); 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 { .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 { .quick-action-card {
transition: var(--transition); transition: var(--transition);
border: 1px solid rgba(0, 0, 0, 0.125); border: 1px solid var(--card-border);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
border-radius: 12px;
} }
.quick-action-card:hover { .quick-action-card:hover {
transform: translateY(-5px); transform: translateY(-3px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
border-color: var(--primary-color);
} }
.quick-action-card .card-body { .quick-action-card .card-body {
padding: 2rem 1.5rem; 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 { .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; border: none;
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
transition: var(--transition); 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-primary:hover,
.card.bg-success:hover, .card.bg-success:hover,
.card.bg-warning:hover, .card.bg-warning:hover,
.card.bg-info:hover { .card.bg-info:hover {
transform: translateY(-2px); transform: translateY(-3px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
} }
/* Таблицы */ /* Таблицы */
.table { .table {
background-color: white; background-color: white;
border-radius: var(--border-radius); border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border: 1px solid var(--card-border);
} }
.table thead th { .table thead th {
background-color: var(--light-color); background: linear-gradient(135deg, var(--light-color) 0%, #e9ecef 100%);
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid var(--primary-color);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.875rem; font-size: 0.875rem;
letter-spacing: 0.5px; letter-spacing: 0.5px;
color: var(--dark-color);
} }
.table tbody tr:hover { .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-control,
.form-select { .form-select {
border-radius: var(--border-radius); border-radius: 8px;
border: 1px solid #ced4da; border: 1px solid #ced4da;
transition: var(--transition); transition: var(--transition);
padding: 0.75rem 1rem;
} }
.form-control:focus, .form-control:focus,
.form-select:focus { .form-select:focus {
border-color: var(--primary-color); 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 { .form-label {
@ -134,15 +250,26 @@ body {
/* Кнопки */ /* Кнопки */
.btn { .btn {
border-radius: var(--border-radius); border-radius: 8px;
font-weight: 500; font-weight: 500;
transition: var(--transition); transition: var(--transition);
text-transform: none; text-transform: none;
border: none;
padding: 0.5rem 1.25rem;
} }
.btn:hover { .btn:hover {
transform: translateY(-1px); transform: translateY(-2px);
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.15); 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 { .btn-primary {
@ -441,3 +568,56 @@ footer {
box-shadow: none !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"> <link href="css/style.css" rel="stylesheet">
</head> </head>
<body> <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"> <div class="container">
<a class="navbar-brand" href="index.html"> <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> </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"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="index.html"> <a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная <i class="fas fa-home me-1"></i>Главная
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="catalog.html"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог <i class="fas fa-boxes me-1"></i>Каталог
</a> </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>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="history.html"> <a class="nav-link active" href="history.html">
<i class="fas fa-history me-1"></i>История <i class="fas fa-history me-1"></i>История
</a> </a>
</li> </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;"> <li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование <i class="fas fa-cog me-1"></i>Администрирование
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li> <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> <li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul> </ul>
</li> </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"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль <i class="fas fa-user me-1"></i>Профиль
</a> </a>
@ -68,6 +80,24 @@
</div> </div>
</nav> </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"> <main class="container mt-4">
<!-- Заголовок страницы --> <!-- Заголовок страницы -->

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// JavaScript для страницы каталога товаров // JavaScript для страницы каталога позиций
class CatalogManager { class CatalogManager {
constructor() { constructor() {
@ -14,10 +14,45 @@ class CatalogManager {
} }
init() { init() {
$(document).ready(() => { $(document).ready(async () => {
this.loadCatalog(); 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.bindEvents();
// Инициализируем фильтры
this.initializeFilters(); 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()); $('#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 { try {
this.showLoading(); this.showLoading();
// Загружаем товары // Загружаем позиции
this.currentItems = await this.warehouse.getAllItems(); this.currentItems = await this.warehouse.getAllItems();
// Загружаем категории // Загружаем категории
const categories = await this.warehouse.getCategories(); const categories = await this.warehouse.getCategories();
if (categories && categories.length > 0) {
this.populateCategoryFilters(categories); this.populateCategoryFilters(categories);
} else {
console.warn('Категории не найдены');
auth.showNotification('Категории не загружены', 'warning');
}
// Применяем фильтры и рендерим // Применяем фильтры и рендерим
this.applyFilters(); this.applyFilters();
@ -83,6 +130,11 @@ class CatalogManager {
// Заполнение фильтра категорий // Заполнение фильтра категорий
populateCategoryFilters(categories) { populateCategoryFilters(categories) {
if (!categories || categories.length === 0) {
console.warn('Нет категорий для отображения');
return;
}
const categoryFilter = $('#categoryFilter'); const categoryFilter = $('#categoryFilter');
const addItemCategory = $('#itemCategory'); const addItemCategory = $('#itemCategory');
const editItemCategory = $('#editItemCategory'); const editItemCategory = $('#editItemCategory');
@ -94,10 +146,12 @@ class CatalogManager {
// Добавляем категории // Добавляем категории
categories.forEach(category => { categories.forEach(category => {
if (category && category.id && category.name) {
const option = `<option value="${category.id}">${category.name}</option>`; const option = `<option value="${category.id}">${category.name}</option>`;
categoryFilter.append(option); categoryFilter.append(option);
addItemCategory.append(option); addItemCategory.append(option);
editItemCategory.append(option); editItemCategory.append(option);
}
}); });
} }
@ -125,7 +179,7 @@ class CatalogManager {
this.filters.inStock = $('#inStockFilter').is(':checked'); this.filters.inStock = $('#inStockFilter').is(':checked');
this.filters.lowStock = $('#lowStockFilter').is(':checked'); this.filters.lowStock = $('#lowStockFilter').is(':checked');
// Фильтруем товары // Фильтруем позиции
this.filteredItems = this.currentItems.filter(item => { this.filteredItems = this.currentItems.filter(item => {
// Поиск по названию, описанию и штрих-коду // Поиск по названию, описанию и штрих-коду
if (this.filters.search) { if (this.filters.search) {
@ -191,7 +245,7 @@ class CatalogManager {
<div class="col-12"> <div class="col-12">
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
<h4>Товары не найдены</h4> <h4>Позиции не найдены</h4>
<p>Попробуйте изменить параметры поиска или фильтры</p> <p>Попробуйте изменить параметры поиска или фильтры</p>
</div> </div>
</div> </div>
@ -205,7 +259,7 @@ class CatalogManager {
const endIndex = startIndex + this.itemsPerPage; const endIndex = startIndex + this.itemsPerPage;
const pageItems = this.filteredItems.slice(startIndex, endIndex); const pageItems = this.filteredItems.slice(startIndex, endIndex);
// Рендерим товары // Рендерим позиции
pageItems.forEach(item => { pageItems.forEach(item => {
const itemHtml = this.currentView === 'grid' ? const itemHtml = this.currentView === 'grid' ?
this.createGridItem(item) : this.createListItem(item); this.createGridItem(item) : this.createListItem(item);
@ -216,7 +270,7 @@ class CatalogManager {
this.renderPagination(totalPages); this.renderPagination(totalPages);
} }
// Создание карточки товара (сетка) // Создание карточки позиции (сетка)
createGridItem(item) { createGridItem(item) {
const category = this.getCategoryInfo(item.category); const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item); const stockStatus = this.getStockStatus(item);
@ -248,7 +302,7 @@ class CatalogManager {
<button class="btn btn-outline-primary btn-sm" onclick="catalogManager.viewItem(${item.id})"> <button class="btn btn-outline-primary btn-sm" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-1"></i>Просмотр <i class="fas fa-eye me-1"></i>Просмотр
</button> </button>
${auth.hasPermission('write') ? ` ${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
<button class="btn btn-outline-warning btn-sm" onclick="catalogManager.editItem(${item.id})"> <button class="btn btn-outline-warning btn-sm" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-1"></i>Редактировать <i class="fas fa-edit me-1"></i>Редактировать
</button> </button>
@ -260,7 +314,7 @@ class CatalogManager {
`; `;
} }
// Создание строки товара (список) // Создание строки позиции (список)
createListItem(item) { createListItem(item) {
const category = this.getCategoryInfo(item.category); const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item); const stockStatus = this.getStockStatus(item);
@ -300,12 +354,12 @@ class CatalogManager {
<li><a class="dropdown-item" href="#" onclick="catalogManager.viewItem(${item.id})"> <li><a class="dropdown-item" href="#" onclick="catalogManager.viewItem(${item.id})">
<i class="fas fa-eye me-2"></i>Просмотр <i class="fas fa-eye me-2"></i>Просмотр
</a></li> </a></li>
${auth.hasPermission('write') ? ` ${(auth.hasRole('admin') || auth.hasRole('manager')) ? `
<li><a class="dropdown-item" href="#" onclick="catalogManager.editItem(${item.id})"> <li><a class="dropdown-item" href="#" onclick="catalogManager.editItem(${item.id})">
<i class="fas fa-edit me-2"></i>Редактировать <i class="fas fa-edit me-2"></i>Редактировать
</a></li> </a></li>
` : ''} ` : ''}
${auth.hasPermission('delete') ? ` ${auth.hasRole('admin') ? `
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="catalogManager.deleteItem(${item.id})"> <li><a class="dropdown-item text-danger" href="#" onclick="catalogManager.deleteItem(${item.id})">
<i class="fas fa-trash me-2"></i>Удалить <i class="fas fa-trash me-2"></i>Удалить
@ -370,10 +424,14 @@ class CatalogManager {
// Обновление статистики // Обновление статистики
updateStatistics() { updateStatistics() {
if (!this.filteredItems) {
this.filteredItems = [];
}
const totalItems = this.filteredItems.length; const totalItems = this.filteredItems.length;
const inStockItems = this.filteredItems.filter(item => item.quantity > 0).length; const inStockItems = this.filteredItems.filter(item => item && item.quantity > 0).length;
const lowStockItems = this.filteredItems.filter(item => item.quantity <= item.minQuantity).length; const lowStockItems = this.filteredItems.filter(item => item && item.quantity <= (item.minQuantity || 0)).length;
const categories = [...new Set(this.filteredItems.map(item => item.category))].length; const categories = [...new Set(this.filteredItems.filter(item => item && item.category).map(item => item.category))].length;
$('#totalItemsCount').text(totalItems); $('#totalItemsCount').text(totalItems);
$('#inStockCount').text(inStockItems); $('#inStockCount').text(inStockItems);
@ -383,17 +441,21 @@ class CatalogManager {
// Получение информации о категории // Получение информации о категории
getCategoryInfo(categoryId) { getCategoryInfo(categoryId) {
const categories = [ if (!categoryId) {
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' }, return { id: 'unknown', name: 'Без категории', icon: 'fas fa-box' };
{ 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' }
];
return categories.find(cat => cat.id === categoryId) || // Используем категории из warehouse
{ id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' }; 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 { id: 'unknown', name: 'Неизвестная категория', icon: 'fas fa-box' };
} }
// Получение статуса запаса // Получение статуса запаса
@ -407,16 +469,16 @@ class CatalogManager {
} }
} }
// Просмотр товара // Просмотр позиции
viewItem(itemId) { viewItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId); const item = this.currentItems.find(i => i.id === itemId);
if (!item) return; if (!item) return;
// Здесь можно открыть модальное окно с подробной информацией // Здесь можно открыть модальное окно с подробной информацией
auth.showNotification(`Просмотр товара: ${item.name}`, 'info'); auth.showNotification(`Просмотр позиции: ${item.name}`, 'info');
} }
// Редактирование товара // Редактирование позиции
async editItem(itemId) { async editItem(itemId) {
const item = this.currentItems.find(i => i.id === itemId); const item = this.currentItems.find(i => i.id === itemId);
if (!item) return; if (!item) return;
@ -467,19 +529,19 @@ class CatalogManager {
this.currentItems[itemIndex] = { ...this.currentItems[itemIndex], ...updateData }; this.currentItems[itemIndex] = { ...this.currentItems[itemIndex], ...updateData };
} }
auth.showNotification('Товар успешно обновлен', 'success'); auth.showNotification('Позиция успешно обновлена', 'success');
// Закрываем модальное окно и обновляем каталог // Закрываем модальное окно и обновляем каталог
$('#editItemModal').modal('hide'); $('#editItemModal').modal('hide');
this.applyFilters(); this.applyFilters();
} catch (error) { } catch (error) {
console.error('Ошибка при обновлении товара:', error); console.error('Ошибка при обновлении позиции:', error);
auth.showNotification('Ошибка при обновлении товара', 'danger'); auth.showNotification('Ошибка при обновлении позиции', 'danger');
} }
} }
// Добавление нового товара // Добавление новой позиции
async addNewItem() { async addNewItem() {
const formData = this.getFormData('#addItemForm'); const formData = this.getFormData('#addItemForm');
@ -493,7 +555,7 @@ class CatalogManager {
// Добавляем в локальные данные // Добавляем в локальные данные
this.currentItems.push(newItem); this.currentItems.push(newItem);
auth.showNotification('Товар успешно добавлен', 'success'); auth.showNotification('Позиция успешно добавлена', 'success');
// Закрываем модальное окно и обновляем каталог // Закрываем модальное окно и обновляем каталог
$('#addItemModal').modal('hide'); $('#addItemModal').modal('hide');
@ -503,14 +565,14 @@ class CatalogManager {
$('#addItemForm')[0].reset(); $('#addItemForm')[0].reset();
} catch (error) { } catch (error) {
console.error('Ошибка при добавлении товара:', error); console.error('Ошибка при добавлении позиции:', error);
auth.showNotification('Ошибка при добавлении товара', 'danger'); auth.showNotification('Ошибка при добавлении позиции', 'danger');
} }
} }
// Удаление товара // Удаление позиции
async deleteItem(itemId) { async deleteItem(itemId) {
if (!confirm('Вы уверены, что хотите удалить этот товар?')) { if (!confirm('Вы уверены, что хотите удалить эту позицию?')) {
return; return;
} }
@ -520,12 +582,12 @@ class CatalogManager {
// Удаляем из локальных данных // Удаляем из локальных данных
this.currentItems = this.currentItems.filter(i => i.id !== itemId); this.currentItems = this.currentItems.filter(i => i.id !== itemId);
auth.showNotification('Товар успешно удален', 'success'); auth.showNotification('Позиция успешно удалена', 'success');
this.applyFilters(); this.applyFilters();
} catch (error) { } catch (error) {
console.error('Ошибка при удалении товара:', error); console.error('Ошибка при удалении позиции:', error);
auth.showNotification('Ошибка при удалении товара', 'danger'); auth.showNotification('Ошибка при удалении позиции', 'danger');
} }
} }
@ -632,6 +694,126 @@ class CatalogManager {
hideLoading() { hideLoading() {
// Загрузка завершается в renderCatalog() // Загрузка завершается в 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() { init() {
$(document).ready(() => { $(document).ready(async () => {
this.loadHistory(); 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.bindEvents();
// Инициализируем фильтры
this.initializeFilters(); this.initializeFilters();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
}); });
} }
@ -32,6 +61,13 @@ class HistoryManager {
$('#exportBtn').on('click', () => this.exportOperations()); $('#exportBtn').on('click', () => this.exportOperations());
$('#generateReportBtn').on('click', () => this.generateReport()); $('#generateReportBtn').on('click', () => this.generateReport());
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
// Установка дат по умолчанию // Установка дат по умолчанию
this.setDefaultDates(); this.setDefaultDates();
} }
@ -44,7 +80,7 @@ class HistoryManager {
// Загружаем операции // Загружаем операции
this.operations = await this.warehouse.getOperations(); this.operations = await this.warehouse.getOperations();
// Загружаем товары для фильтра // Загружаем позиции для фильтра
const items = await this.warehouse.getAllItems(); const items = await this.warehouse.getAllItems();
this.populateItemFilter(items); this.populateItemFilter(items);
@ -89,7 +125,7 @@ class HistoryManager {
$('#reportEndDate').val(today.toISOString().split('T')[0]); $('#reportEndDate').val(today.toISOString().split('T')[0]);
} }
// Заполнение фильтра товаров // Заполнение фильтра позиций
populateItemFilter(items) { populateItemFilter(items) {
const itemFilter = $('#itemFilter'); const itemFilter = $('#itemFilter');
const reportItem = $('#reportItem'); const reportItem = $('#reportItem');
@ -147,7 +183,7 @@ class HistoryManager {
} }
} }
// Фильтр по товару // Фильтр по позиции
if (this.filters.itemId && operation.itemId !== parseInt(this.filters.itemId)) { if (this.filters.itemId && operation.itemId !== parseInt(this.filters.itemId)) {
return false; return false;
} }
@ -405,10 +441,10 @@ class HistoryManager {
</table> </table>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h6>Детали товара</h6> <h6>Детали позиции</h6>
<table class="table table-sm"> <table class="table table-sm">
<tr> <tr>
<td><strong>Товар:</strong></td> <td><strong>Позиция:</strong></td>
<td>${operation.itemName}</td> <td>${operation.itemName}</td>
</tr> </tr>
<tr> <tr>
@ -614,6 +650,78 @@ class HistoryManager {
hideLoading() { hideLoading() {
// Загрузка завершается в renderOperations() // Загрузка завершается в 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() { init() {
// Инициализация после загрузки DOM // Инициализация после загрузки DOM
$(document).ready(() => { $(document).ready(async () => {
this.loadDashboardData(); 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.bindEvents();
// Инициализируем модальные окна
this.initializeModals(); this.initializeModals();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
}); });
} }
@ -29,6 +63,13 @@ class MainApp {
// Обработчики для экспорта // Обработчики для экспорта
$('#exportBtn').on('click', () => this.exportData()); $('#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(); this.showLoading();
// Загружаем статистику // Загружаем статистику
const stats = await this.warehouse.getStatistics(); const stats = this.warehouse.getStatistics();
this.updateStatistics(stats); this.updateStatistics(stats);
// Загружаем последние операции // Загружаем последние операции
const operations = await this.warehouse.getRecentOperations(10); const operations = await this.warehouse.getRecentOperations(10);
this.updateOperationsTable(operations); this.updateOperationsTable(operations);
// Загружаем товары для модальных окон // Загружаем позиции для модальных окон
const items = await this.warehouse.getAllItems(); const items = await this.warehouse.getAllItems();
this.populateItemSelects(items); this.populateItemSelects(items);
@ -146,7 +187,7 @@ class MainApp {
}); });
} }
// Заполнение селектов товаров // Заполнение селектов позиций
populateItemSelects(items) { populateItemSelects(items) {
const selects = ['#incomingItem', '#outgoingItem']; const selects = ['#incomingItem', '#outgoingItem'];
@ -160,7 +201,7 @@ class MainApp {
}); });
} }
// Добавление нового товара // Добавление новой позиции
async addNewItem() { async addNewItem() {
const formData = this.getFormData('#addItemForm'); const formData = this.getFormData('#addItemForm');
@ -170,7 +211,7 @@ class MainApp {
try { try {
const newItem = await this.warehouse.addItem(formData); const newItem = await this.warehouse.addItem(formData);
auth.showNotification('Товар успешно добавлен', 'success'); auth.showNotification('Позиция успешно добавлена', 'success');
// Закрываем модальное окно // Закрываем модальное окно
$('#addItemModal').modal('hide'); $('#addItemModal').modal('hide');
@ -182,8 +223,8 @@ class MainApp {
$('#addItemForm')[0].reset(); $('#addItemForm')[0].reset();
} catch (error) { } catch (error) {
console.error('Ошибка при добавлении товара:', error); console.error('Ошибка при добавлении позиции:', error);
auth.showNotification('Ошибка при добавлении товара', 'danger'); auth.showNotification('Ошибка при добавлении позиции', 'danger');
} }
} }
@ -424,6 +465,92 @@ class MainApp {
hideLoading() { hideLoading() {
$('#loadingOverlay').remove(); $('#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() { init() {
$(document).ready(() => { $(document).ready(async () => {
this.loadProfile(); 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(); this.bindEvents();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
}); });
} }
@ -30,6 +57,13 @@ class ProfileManager {
e.preventDefault(); e.preventDefault();
this.saveProfile(); this.saveProfile();
}); });
// Обработчик выхода
$('#logoutBtn').on('click', (e) => {
e.preventDefault();
auth.logout();
window.location.href = 'login.html';
});
} }
// Загрузка профиля // Загрузка профиля
@ -427,6 +461,78 @@ class ProfileManager {
return recommendations; 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() { init() {
$(document).ready(() => { $(document).ready(async () => {
this.loadCategories(); 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.bindEvents();
// Инициализируем фильтры
this.initializeFilters(); this.initializeFilters();
} catch (error) {
console.error('Ошибка инициализации:', error);
auth.showNotification('Ошибка загрузки данных', 'danger');
}
}); });
} }
@ -45,6 +74,13 @@ class SearchManager {
e.preventDefault(); e.preventDefault();
$(e.target).tab('show'); $(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(); this.filters.endDate = $('#endDate').val();
} }
// Поиск товаров // Поиск позиций
async searchItems(query) { async searchItems(query) {
if (this.filters.searchType === 'operations') { if (this.filters.searchType === 'operations') {
this.searchResults.items = []; this.searchResults.items = [];
@ -218,7 +254,7 @@ class SearchManager {
<div class="col-12"> <div class="col-12">
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-box-open"></i> <i class="fas fa-box-open"></i>
<h5>Товары не найдены</h5> <h5>Позиции не найдены</h5>
<p>Попробуйте изменить поисковый запрос</p> <p>Попробуйте изменить поисковый запрос</p>
</div> </div>
</div> </div>
@ -226,7 +262,7 @@ class SearchManager {
return; return;
} }
// Пагинация для товаров // Пагинация для позиций
const totalPages = Math.ceil(this.searchResults.items.length / this.resultsPerPage); const totalPages = Math.ceil(this.searchResults.items.length / this.resultsPerPage);
const startIndex = (this.currentPage - 1) * this.resultsPerPage; const startIndex = (this.currentPage - 1) * this.resultsPerPage;
const endIndex = startIndex + this.resultsPerPage; const endIndex = startIndex + this.resultsPerPage;
@ -238,7 +274,7 @@ class SearchManager {
}); });
} }
// Создание карточки товара // Создание карточки позиции
createItemCard(item) { createItemCard(item) {
const category = this.getCategoryInfo(item.category); const category = this.getCategoryInfo(item.category);
const stockStatus = this.getStockStatus(item); const stockStatus = this.getStockStatus(item);
@ -404,7 +440,7 @@ class SearchManager {
return; return;
} }
// Сортировка товаров // Сортировка позиций
this.searchResults.items.sort((a, b) => { this.searchResults.items.sort((a, b) => {
let aValue = a[sortBy]; let aValue = a[sortBy];
let bValue = b[sortBy]; let bValue = b[sortBy];
@ -475,7 +511,7 @@ class SearchManager {
csv += `Запрос: ${this.currentQuery}\n`; csv += `Запрос: ${this.currentQuery}\n`;
csv += `Дата поиска: ${new Date().toLocaleDateString('ru-RU')}\n\n`; csv += `Дата поиска: ${new Date().toLocaleDateString('ru-RU')}\n\n`;
// Экспорт товаров // Экспорт позиций
if (this.searchResults.items.length > 0) { if (this.searchResults.items.length > 0) {
csv += 'ТОВАРЫ\n'; csv += 'ТОВАРЫ\n';
csv += 'ID,Название,Категория,Цена,Количество,Описание\n'; csv += 'ID,Название,Категория,Цена,Количество,Описание\n';
@ -513,7 +549,7 @@ class SearchManager {
} }
} }
// Просмотр деталей товара // Просмотр деталей позиции
async viewItemDetails(itemId) { async viewItemDetails(itemId) {
try { try {
const item = await this.warehouse.getItemById(itemId); const item = await this.warehouse.getItemById(itemId);
@ -587,8 +623,8 @@ class SearchManager {
$('#itemDetailsModal').modal('show'); $('#itemDetailsModal').modal('show');
} catch (error) { } catch (error) {
console.error('Ошибка при загрузке деталей товара:', error); console.error('Ошибка при загрузке деталей позиции:', error);
auth.showNotification('Ошибка при загрузке деталей товара', 'danger'); auth.showNotification('Ошибка при загрузке деталей позиции', 'danger');
} }
} }
@ -661,6 +697,78 @@ class SearchManager {
hideLoading() { hideLoading() {
$('#loadingState').hide(); $('#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(); this.categories = this.loadCategories();
} }
// Загрузка товаров из localStorage // Загрузка позиций из localStorage
loadItems() { loadItems() {
const items = localStorage.getItem('warehouse_items'); const items = localStorage.getItem('warehouse_items');
return items ? JSON.parse(items) : this.getDefaultItems(); return items ? JSON.parse(items) : this.getDefaultItems();
@ -21,16 +21,88 @@ class WarehouseManager {
// Загрузка категорий // Загрузка категорий
loadCategories() { 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 [ return [
{ id: 'electronics', name: 'Электроника', icon: 'fas fa-laptop' }, {
{ id: 'clothing', name: 'Одежда', icon: 'fas fa-tshirt' }, id: 'electronics',
{ id: 'tools', name: 'Инструменты', icon: 'fas fa-tools' }, name: 'Электроника',
{ id: 'office', name: 'Офисные принадлежности', icon: 'fas fa-briefcase' }, description: 'Электронные компоненты и устройства',
{ id: 'furniture', name: 'Мебель', icon: 'fas fa-couch' }, icon: 'fas fa-microchip',
{ id: 'books', name: 'Книги', icon: 'fas fa-book' } 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 // Сохранение данных в localStorage
saveItems() { saveItems() {
localStorage.setItem('warehouse_items', JSON.stringify(this.items)); localStorage.setItem('warehouse_items', JSON.stringify(this.items));
@ -40,7 +112,7 @@ class WarehouseManager {
localStorage.setItem('warehouse_operations', JSON.stringify(this.operations)); localStorage.setItem('warehouse_operations', JSON.stringify(this.operations));
} }
// Получение товаров по умолчанию // Получение позиций по умолчанию
getDefaultItems() { getDefaultItems() {
return [ return [
{ {
@ -54,8 +126,8 @@ class WarehouseManager {
minQuantity: 5, minQuantity: 5,
location: 'Стеллаж A-1', location: 'Стеллаж A-1',
supplier: 'Dell Inc.', supplier: 'Dell Inc.',
createdAt: '2024-01-15T10:00:00Z', createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2024-01-15T10:00:00Z' updatedAt: '2025-01-15T10:00:00Z'
}, },
{ {
id: 2, id: 2,
@ -68,13 +140,13 @@ class WarehouseManager {
minQuantity: 3, minQuantity: 3,
location: 'Стеллаж A-2', location: 'Стеллаж A-2',
supplier: 'HP Inc.', supplier: 'HP Inc.',
createdAt: '2024-01-10T14:30:00Z', createdAt: '2025-01-10T14:30:00Z',
updatedAt: '2024-01-10T14:30:00Z' updatedAt: '2025-01-10T14:30:00Z'
}, },
{ {
id: 3, id: 3,
name: 'Офисный стул', name: 'Офисный стул',
category: 'furniture', category: 'office',
price: 3500, price: 3500,
quantity: 25, quantity: 25,
description: 'Офисный стул с подлокотниками, черный', description: 'Офисный стул с подлокотниками, черный',
@ -82,8 +154,8 @@ class WarehouseManager {
minQuantity: 10, minQuantity: 10,
location: 'Стеллаж B-1', location: 'Стеллаж B-1',
supplier: 'МебельСтрой', supplier: 'МебельСтрой',
createdAt: '2024-01-05T09:15:00Z', createdAt: '2025-01-05T09:15:00Z',
updatedAt: '2024-01-05T09:15:00Z' updatedAt: '2025-01-05T09:15:00Z'
}, },
{ {
id: 4, id: 4,
@ -96,8 +168,8 @@ class WarehouseManager {
minQuantity: 20, minQuantity: 20,
location: 'Стеллаж C-1', location: 'Стеллаж C-1',
supplier: 'ОфисМаркет', supplier: 'ОфисМаркет',
createdAt: '2024-01-12T11:45:00Z', createdAt: '2025-01-12T11:45:00Z',
updatedAt: '2024-01-12T11:45:00Z' updatedAt: '2025-01-12T11:45:00Z'
}, },
{ {
id: 5, id: 5,
@ -110,14 +182,14 @@ class WarehouseManager {
minQuantity: 15, minQuantity: 15,
location: 'Стеллаж D-1', location: 'Стеллаж D-1',
supplier: 'ИнструментСтрой', supplier: 'ИнструментСтрой',
createdAt: '2024-01-08T16:20:00Z', createdAt: '2025-01-08T16:20:00Z',
updatedAt: '2024-01-08T16:20:00Z' updatedAt: '2025-01-08T16:20:00Z'
} }
]; ];
} }
// Получение статистики // Получение статистики
async getStatistics() { getStatistics() {
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
const totalItems = this.items.reduce((sum, item) => sum + item.quantity, 0); const totalItems = this.items.reduce((sum, item) => sum + item.quantity, 0);
@ -137,17 +209,22 @@ class WarehouseManager {
}; };
} }
// Получение всех товаров // Получение всех позиций
async getAllItems() { async getAllItems() {
return this.items; return this.items;
} }
// Получение товара по ID // Синхронное получение всех позиций
getAllItemsSync() {
return this.items;
}
// Получение позиции по ID
async getItemById(id) { async getItemById(id) {
return this.items.find(item => item.id === parseInt(id)); return this.items.find(item => item.id === parseInt(id));
} }
// Добавление нового товара // Добавление новой позиции
async addItem(itemData) { async addItem(itemData) {
const newItem = { const newItem = {
id: this.getNextItemId(), id: this.getNextItemId(),
@ -167,22 +244,22 @@ class WarehouseManager {
this.items.push(newItem); this.items.push(newItem);
this.saveItems(); this.saveItems();
// Создаем операцию прихода для нового товара // Создаем операцию прихода для новой позиции
await this.createOperation({ await this.createOperation({
type: 'incoming', type: 'incoming',
itemId: newItem.id, itemId: newItem.id,
quantity: newItem.quantity, quantity: newItem.quantity,
notes: 'Добавление нового товара' notes: 'Добавление новой позиции'
}); });
return newItem; return newItem;
} }
// Обновление товара // Обновление позиции
async updateItem(id, itemData) { async updateItem(id, itemData) {
const itemIndex = this.items.findIndex(item => item.id === parseInt(id)); const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
if (itemIndex === -1) { if (itemIndex === -1) {
throw new Error('Товар не найден'); throw new Error('Позиция не найдена');
} }
this.items[itemIndex] = { this.items[itemIndex] = {
@ -195,11 +272,11 @@ class WarehouseManager {
return this.items[itemIndex]; return this.items[itemIndex];
} }
// Удаление товара // Удаление позиции
async deleteItem(id) { async deleteItem(id) {
const itemIndex = this.items.findIndex(item => item.id === parseInt(id)); const itemIndex = this.items.findIndex(item => item.id === parseInt(id));
if (itemIndex === -1) { if (itemIndex === -1) {
throw new Error('Товар не найден'); throw new Error('Позиция не найдена');
} }
this.items.splice(itemIndex, 1); this.items.splice(itemIndex, 1);
@ -214,10 +291,10 @@ class WarehouseManager {
const item = await this.getItemById(itemId); const item = await this.getItemById(itemId);
if (!item) { if (!item) {
throw new Error('Товар не найден'); throw new Error('Позиция не найдена');
} }
// Обновляем количество товара // Обновляем количество позиции
item.quantity += quantity; item.quantity += quantity;
item.updatedAt = new Date().toISOString(); item.updatedAt = new Date().toISOString();
this.saveItems(); this.saveItems();
@ -241,15 +318,15 @@ class WarehouseManager {
const item = await this.getItemById(itemId); const item = await this.getItemById(itemId);
if (!item) { if (!item) {
throw new Error('Товар не найден'); throw new Error('Позиция не найдена');
} }
// Проверяем достаточность товара // Проверяем достаточность позиции
if (item.quantity < quantity) { if (item.quantity < quantity) {
throw new Error(`Недостаточно товара. Доступно: ${item.quantity} шт.`); throw new Error(`Недостаточно позиций. Доступно: ${item.quantity} шт.`);
} }
// Обновляем количество товара // Обновляем количество позиции
item.quantity -= quantity; item.quantity -= quantity;
item.updatedAt = new Date().toISOString(); item.updatedAt = new Date().toISOString();
this.saveItems(); this.saveItems();
@ -401,18 +478,63 @@ class WarehouseManager {
return this.categories; return this.categories;
} }
// Получение категории по ID
async getCategoryById(id) {
return this.categories.find(cat => cat.id === id);
}
// Добавление категории // Добавление категории
async addCategory(categoryData) { async addCategory(categoryData) {
const newCategory = { const newCategory = {
id: categoryData.id || this.generateCategoryId(), id: this.generateCategoryId(),
name: categoryData.name, ...categoryData,
icon: categoryData.icon || 'fas fa-box' itemCount: 0,
createdAt: new Date().toISOString()
}; };
this.categories.push(newCategory); this.categories.push(newCategory);
this.saveCategories();
return newCategory; 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 для товара // Генерация следующего ID для товара
getNextItemId() { getNextItemId() {
const maxId = Math.max(...this.items.map(item => item.id), 0); 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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet">
</head> </head>
<body> <body class="d-flex flex-column min-vh-100">
<div class="login-container"> <div class="login-container flex-grow-1">
<div class="login-card"> <div class="login-card">
<div class="login-header"> <div class="login-header">
<i class="fas fa-warehouse fa-3x mb-3"></i> <i class="fas fa-warehouse fa-3x mb-3"></i>
@ -17,22 +17,30 @@
<p class="mb-0">Система управления складом</p> <p class="mb-0">Система управления складом</p>
</div> </div>
<div class="login-body"> <div class="login-body">
<form id="loginForm"> <form id="loginForm" novalidate>
<div class="mb-3"> <div class="mb-3">
<label for="username" class="form-label"> <label for="username" class="form-label">
<i class="fas fa-user me-2"></i>Имя пользователя <i class="fas fa-user me-2"></i>Имя пользователя
</label> </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>
<div class="mb-4"> <div class="mb-4">
<label for="password" class="form-label"> <label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Пароль <i class="fas fa-lock me-2"></i>Пароль
</label> </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> </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>Войти в систему <i class="fas fa-sign-in-alt me-2"></i>Войти в систему
</button> </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> </form>
<!-- Информация о тестовых пользователях --> <!-- Информация о тестовых пользователях -->
@ -100,9 +108,77 @@
</div> </div>
</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://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="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="js/auth.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> </body>
</html> </html>

View File

@ -9,51 +9,63 @@
<link href="css/style.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet">
</head> </head>
<body> <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"> <div class="container">
<a class="navbar-brand" href="index.html"> <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> </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"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="index.html"> <a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная <i class="fas fa-home me-1"></i>Главная
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="catalog.html"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог <i class="fas fa-boxes me-1"></i>Каталог
</a> </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>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="history.html"> <a class="nav-link" href="history.html">
<i class="fas fa-history me-1"></i>История <i class="fas fa-history me-1"></i>История
</a> </a>
</li> </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;"> <li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование <i class="fas fa-cog me-1"></i>Администрирование
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li> <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> <li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul> </ul>
</li> </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"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль <i class="fas fa-user me-1"></i>Профиль
</a> </a>
@ -68,6 +80,24 @@
</div> </div>
</nav> </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="profile-header">
<div class="container"> <div class="container">

View File

@ -9,51 +9,63 @@
<link href="css/style.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet">
</head> </head>
<body> <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"> <div class="container">
<a class="navbar-brand" href="index.html"> <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> </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"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="index.html"> <a class="nav-link" href="index.html">
<i class="fas fa-home me-1"></i>Главная <i class="fas fa-home me-1"></i>Главная
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item dropdown">
<a class="nav-link" href="catalog.html"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-boxes me-1"></i>Каталог <i class="fas fa-boxes me-1"></i>Каталог
</a> </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>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="history.html"> <a class="nav-link" href="history.html">
<i class="fas fa-history me-1"></i>История <i class="fas fa-history me-1"></i>История
</a> </a>
</li> </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;"> <li class="nav-item dropdown" id="adminDropdown" style="display: none;">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Администрирование <i class="fas fa-cog me-1"></i>Администрирование
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="admin/users.html">Управление пользователями</a></li> <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> <li><a class="dropdown-item" href="admin/reports.html">Отчеты</a></li>
</ul> </ul>
</li> </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"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i>Профиль <i class="fas fa-user me-1"></i>Профиль
</a> </a>
@ -68,6 +80,24 @@
</div> </div>
</nav> </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"> <main class="container mt-4">
<!-- Заголовок страницы --> <!-- Заголовок страницы -->
@ -176,7 +206,7 @@
<ul class="nav nav-tabs mb-3" id="resultsTabs"> <ul class="nav nav-tabs mb-3" id="resultsTabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" id="items-tab" data-bs-toggle="tab" href="#itemsResults"> <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> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
@ -233,7 +263,7 @@
<div id="emptyState" class="text-center py-5"> <div id="emptyState" class="text-center py-5">
<i class="fas fa-search fa-3x text-muted mb-3"></i> <i class="fas fa-search fa-3x text-muted mb-3"></i>
<h4>Начните поиск</h4> <h4>Начните поиск</h4>
<p class="text-muted">Введите запрос в поле поиска выше, чтобы найти товары или операции</p> <p class="text-muted">Введите запрос в поле поиска выше, чтобы найти позиции или операции</p>
</div> </div>
<!-- Состояние загрузки --> <!-- Состояние загрузки -->
@ -250,7 +280,7 @@
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<i class="fas fa-box me-2"></i> <i class="fas fa-box me-2"></i>
Детали товара Детали позиции
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>