/
Centermant
/
web-nodejs
Обзор
Документация
Войти
/
Centermant
/
web-nodejs
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
public/lab10/task1/js/admin.js
816 строк
35 KB
Centermant
feat(lab9.1 final)
04 дек 2025, 00:36
04 дек 2025, 00:36
8ccd8ce
Код
Авторство
О чём код?
document.addEventListener('DOMContentLoaded', () => { // Элементы DOM const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); const productsTableBody = document.getElementById('productsTableBody'); const usersTableBody = document.getElementById('usersTableBody'); const ordersTableBody = document.getElementById('ordersTableBody'); const productSearch = document.getElementById('productSearch'); const userSearch = document.getElementById('userSearch'); const orderStatusFilter = document.getElementById('orderStatusFilter'); // Модальные окна const productModal = document.getElementById('productModal'); const userModal = document.getElementById('userModal'); const orderModal = document.getElementById('orderModal'); const statusModal = document.getElementById('statusModal'); const confirmModal = document.getElementById('confirmModal'); // Формы и кнопки const productForm = document.getElementById('productForm'); const userForm = document.getElementById('userForm'); const statusForm = document.getElementById('statusForm'); const addProductBtn = document.getElementById('addProductBtn'); const addUserBtn = document.getElementById('addUserBtn'); const closeProductModal = document.getElementById('closeProductModal'); const closeUserModal = document.getElementById('closeUserModal'); const closeOrderModal = document.getElementById('closeOrderModal'); const closeStatusModal = document.getElementById('closeStatusModal'); const closeConfirmModal = document.getElementById('closeConfirmModal'); const cancelProductBtn = document.getElementById('cancelProductBtn'); const cancelUserBtn = document.getElementById('cancelUserBtn'); const cancelStatusBtn = document.getElementById('cancelStatusBtn'); const cancelConfirmBtn = document.getElementById('cancelConfirmBtn'); const confirmDeleteBtn = document.getElementById('confirmDeleteBtn'); const closeOrderDetailsBtn = document.getElementById('closeOrderDetailsBtn'); // Состояние приложения let currentTab = 'products'; let editingProductId = null; let editingUserId = null; let currentDeleteId = null; let currentDeleteType = null; let currentOrderId = null; // Функция форматирования цены function formatPrice(price) { return new Intl.NumberFormat('ru-RU').format(price) + ' ₽'; } // Функция форматирования даты function formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return new Intl.DateTimeFormat('ru-RU', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(date); } // Функция переключения вкладок function switchTab(tabName) { // Обновляем кнопки tabBtns.forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); // Обновляем контент tabContents.forEach(content => { content.classList.toggle('active', content.id === `${tabName}-tab`); }); currentTab = tabName; // Загружаем данные для активной вкладки switch(tabName) { case 'products': loadProducts(); break; case 'users': loadUsers(); break; case 'orders': loadOrders(); break; } } // Функция отображения модального окна function showModal(modal) { modal.classList.add('show'); document.body.style.overflow = 'hidden'; } // Функция скрытия модального окна function hideModal(modal) { modal.classList.remove('show'); document.body.style.overflow = ''; } // Функция загрузки товаров async function loadProducts(searchQuery = '') { productsTableBody.innerHTML = '<tr><td colspan="6" class="loading">Загрузка товаров...</td></tr>'; try { const response = await fetch(`/api/lab10/admin/products`); const products = await response.json(); if (response.ok) { displayProducts(products.filter(product => !searchQuery || product.title.toLowerCase().includes(searchQuery.toLowerCase()) || product.category_name.toLowerCase().includes(searchQuery.toLowerCase()) )); } else { productsTableBody.innerHTML = `<tr><td colspan="6" class="error">Ошибка загрузки товаров: ${products.error || 'Неизвестная ошибка'}</td></tr>`; } } catch (error) { productsTableBody.innerHTML = '<tr><td colspan="6" class="error">Ошибка сети. Попробуйте позже.</td></tr>'; console.error('Ошибка загрузки товаров:', error); } } async function checkAuth() { try { // Сначала проверяем localStorage const userFromStorage = localStorage.getItem('user'); if (userFromStorage) { const user = JSON.parse(userFromStorage); if (user.role === 'admin') { return true; } } // Если данных в localStorage нет, проверяем через API const response = await fetch('/api/lab10/auth/check'); const data = await response.json(); if (data.isLoggedIn && data.user.role === 'admin') { // Сохраняем данные в localStorage localStorage.setItem('user', JSON.stringify(data.user)); return true; } return false; } catch (error) { console.error('Ошибка проверки аутентификации:', error); return false; } } // Проверка авторизации при загрузке админ-панели document.addEventListener('DOMContentLoaded', async () => { const isAuthenticated = await checkAuth(); if (!isAuthenticated) { // Перенаправляем на главную страницу, если пользователь не администратор window.location.href = '/lab10/task1/'; return; } // Инициализация switchTab('products'); }); // Функция отображения товаров function displayProducts(products) { if (products.length === 0) { productsTableBody.innerHTML = '<tr><td colspan="6" class="no-data">Товары не найдены</td></tr>'; return; } productsTableBody.innerHTML = products.map(product => ` <tr> <td>${product.id}</td> <td>${product.title}</td> <td>${formatPrice(product.price)}</td> <td>${product.category_name || 'Без категории'}</td> <td>${product.amount}</td> <td> <div class="action-buttons"> <button class="action-btn edit-btn" data-id="${product.id}" title="Редактировать"> <i class="fas fa-edit"></i> </button> <button class="action-btn delete-btn" data-id="${product.id}" title="Удалить"> <i class="fas fa-trash"></i> </button> </div> </td> </tr> `).join(''); // Добавляем обработчики событий document.querySelectorAll('.edit-btn').forEach(btn => { btn.addEventListener('click', () => editProduct(btn.dataset.id)); }); document.querySelectorAll('.delete-btn').forEach(btn => { btn.addEventListener('click', () => confirmDelete('product', btn.dataset.id)); }); } // Функция загрузки пользователей async function loadUsers(searchQuery = '') { usersTableBody.innerHTML = '<tr><td colspan="6" class="loading">Загрузка пользователей...</td></tr>'; try { const response = await fetch(`/api/lab10/admin/users`); const users = await response.json(); if (response.ok) { displayUsers(users.filter(user => !searchQuery || user.username.toLowerCase().includes(searchQuery.toLowerCase()) || user.email.toLowerCase().includes(searchQuery.toLowerCase()) )); } else { usersTableBody.innerHTML = `<tr><td colspan="6" class="error">Ошибка загрузки пользователей: ${users.error || 'Неизвестная ошибка'}</td></tr>`; } } catch (error) { usersTableBody.innerHTML = '<tr><td colspan="6" class="error">Ошибка сети. Попробуйте позже.</td></tr>'; console.error('Ошибка загрузки пользователей:', error); } } // Функция отображения пользователей function displayUsers(users) { if (users.length === 0) { usersTableBody.innerHTML = '<tr><td colspan="6" class="no-data">Пользователи не найдены</td></tr>'; return; } usersTableBody.innerHTML = users.map(user => ` <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> <td> <span class="status-badge status-${user.role}">${user.role}</span> </td> <td>${formatDate(user.created_at)}</td> <td> <div class="action-buttons"> <button class="action-btn edit-btn" data-id="${user.id}" title="Редактировать"> <i class="fas fa-edit"></i> </button> <button class="action-btn delete-btn" data-id="${user.id}" title="Удалить"> <i class="fas fa-trash"></i> </button> </div> </td> </tr> `).join(''); // Добавляем обработчики событий document.querySelectorAll('.edit-btn').forEach(btn => { btn.addEventListener('click', () => editUser(btn.dataset.id)); }); document.querySelectorAll('.delete-btn').forEach(btn => { btn.addEventListener('click', () => confirmDelete('user', btn.dataset.id)); }); } // Функция загрузки заказов async function loadOrders(status = '') { ordersTableBody.innerHTML = '<tr><td colspan="6" class="loading">Загрузка заказов...</td></tr>'; try { const params = new URLSearchParams(); if (status) params.append('status', status); const response = await fetch(`/api/lab10/admin/orders?${params.toString()}`); const data = await response.json(); if (response.ok) { displayOrders(data.data); } else { ordersTableBody.innerHTML = `<tr><td colspan="6" class="error">Ошибка загрузки заказов: ${data.error || 'Неизвестная ошибка'}</td></tr>`; } } catch (error) { ordersTableBody.innerHTML = '<tr><td colspan="6" class="error">Ошибка сети. Попробуйте позже.</td></tr>'; console.error('Ошибка загрузки заказов:', error); } } // Функция отображения заказов function displayOrders(orders) { if (orders.length === 0) { ordersTableBody.innerHTML = '<tr><td colspan="6" class="no-data">Заказы не найдены</td></tr>'; return; } ordersTableBody.innerHTML = orders.map(order => ` <tr> <td>${order.id}</td> <td>${order.user_name || 'Гость'}</td> <td>${formatPrice(order.total_price)}</td> <td> <span class="status-badge status-${order.status}">${order.status}</span> </td> <td>${formatDate(order.created_at)}</td> <td> <div class="action-buttons"> <button class="action-btn view-btn" data-id="${order.id}" title="Детали"> <i class="fas fa-eye"></i> </button> <button class="action-btn status-btn" data-id="${order.id}" title="Изменить статус"> <i class="fas fa-sync-alt"></i> </button> </div> </td> </tr> `).join(''); // Добавляем обработчики событий document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', () => viewOrderDetails(btn.dataset.id)); }); document.querySelectorAll('.status-btn').forEach(btn => { btn.addEventListener('click', () => changeOrderStatus(btn.dataset.id)); }); } // Функция загрузки категорий для формы товара async function loadCategoriesForForm() { try { const response = await fetch('/api/lab10/categories'); const categories = await response.json(); if (response.ok) { const categorySelect = document.getElementById('productCategory'); categorySelect.innerHTML = '<option value="">Выберите категорию</option>' + categories.map(category => ` <option value="${category.id}">${category.name}</option> `).join(''); } } catch (error) { console.error('Ошибка загрузки категорий:', error); } } // Функция открытия формы добавления товара function openAddProductForm() { editingProductId = null; document.getElementById('productModalTitle').textContent = 'Добавить товар'; document.getElementById('productForm').reset(); document.getElementById('productFormError').textContent = ''; loadCategoriesForForm(); showModal(productModal); } // Функция открытия формы редактирования товара async function editProduct(productId) { try { const response = await fetch(`/api/lab10/products/${productId}`); const product = await response.json(); if (response.ok) { editingProductId = productId; document.getElementById('productModalTitle').textContent = 'Редактировать товар'; document.getElementById('productTitle').value = product.title; document.getElementById('productDescription').value = product.description || ''; document.getElementById('productPrice').value = product.price; document.getElementById('productAmount').value = product.amount; document.getElementById('productImage').value = product.image || ''; document.getElementById('productFormError').textContent = ''; // Загружаем категории и выбираем текущую await loadCategoriesForForm(); document.getElementById('productCategory').value = product.category_id || ''; showModal(productModal); } else { showNotification(`Ошибка загрузки товара: ${product.error || 'Неизвестная ошибка'}`, 'error'); } } catch (error) { showNotification('Ошибка сети. Попробуйте позже.', 'error'); console.error('Ошибка загрузки товара:', error); } } // Функция открытия формы добавления пользователя function openAddUserForm() { editingUserId = null; document.getElementById('userModalTitle').textContent = 'Добавить пользователя'; document.getElementById('userForm').reset(); document.getElementById('userFormError').textContent = ''; showModal(userModal); } // Функция открытия формы редактирования пользователя async function editUser(userId) { try { const response = await fetch(`/api/lab10/admin/users/${userId}`); const user = await response.json(); if (response.ok) { editingUserId = userId; document.getElementById('userModalTitle').textContent = 'Редактировать пользователя'; document.getElementById('userUsername').value = user.username; document.getElementById('userEmail').value = user.email; document.getElementById('userPassword').value = ''; // Не показываем пароль document.getElementById('userRole').value = user.role; document.getElementById('userFormError').textContent = ''; showModal(userModal); } else { showNotification(`Ошибка загрузки пользователя: ${user.error || 'Неизвестная ошибка'}`, 'error'); } } catch (error) { showNotification('Ошибка сети. Попробуйте позже.', 'error'); console.error('Ошибка загрузки пользователя:', error); } } // Функция просмотра деталей заказа async function viewOrderDetails(orderId) { currentOrderId = orderId; document.getElementById('orderId').textContent = orderId; document.getElementById('orderDetailsContent').innerHTML = '<div class="loading">Загрузка деталей заказа...</div>'; try { const response = await fetch(`/api/lab10/admin/orders/${orderId}`); const order = await response.json(); if (response.ok) { displayOrderDetails(order); showModal(orderModal); } else { document.getElementById('orderDetailsContent').innerHTML = ` <div class="error">Ошибка загрузки деталей заказа: ${order.error || 'Неизвестная ошибка'}</div> `; } } catch (error) { document.getElementById('orderDetailsContent').innerHTML = ` <div class="error">Ошибка сети. Попробуйте позже.</div> `; console.error('Ошибка загрузки деталей заказа:', error); } } // Функция отображения деталей заказа function displayOrderDetails(order) { if (!order) { document.getElementById('orderDetailsContent').innerHTML = '<div class="error">Заказ не найден</div>'; return; } const orderItemsHtml = order.items && order.items.length > 0 ? ` <div class="order-items"> <h3>Товары в заказе</h3> <table> <thead> <tr> <th>Товар</th> <th>Количество</th> <th>Цена за единицу</th> <th>Итого</th> </tr> </thead> <tbody> ${order.items.map(item => ` <tr> <td>${item.product_title}</td> <td>${item.quantity}</td> <td>${formatPrice(item.price_at_purchase)}</td> <td>${formatPrice(item.price_at_purchase * item.quantity)}</td> </tr> `).join('')} </tbody> </table> </div> <div class="order-total"> Итого: ${formatPrice(order.total_price)} </div> ` : ` <div class="no-data">В заказе нет товаров</div> `; document.getElementById('orderDetailsContent').innerHTML = ` <div class="order-header"> <div> <h3>Пользователь</h3> <p>${order.user_name || 'Гость'}<br>${order.user_email || '-'}</p> </div> <div> <h3>Статус</h3> <p><span class="status-badge status-${order.status}">${order.status}</span></p> </div> <div> <h3>Дата создания</h3> <p>${formatDate(order.created_at)}</p> </div> <div> <h3>Общая сумма</h3> <p>${formatPrice(order.total_price)}</p> </div> </div> ${orderItemsHtml} `; } // Функция изменения статуса заказа function changeOrderStatus(orderId) { currentOrderId = orderId; document.getElementById('statusOrderId').value = orderId; document.getElementById('statusFormError').textContent = ''; showModal(statusModal); } // Функция подтверждения удаления function confirmDelete(type, id) { currentDeleteType = type; currentDeleteId = id; let message = ''; switch(type) { case 'product': message = 'Вы уверены, что хотите удалить этот товар? Это действие нельзя отменить.'; break; case 'user': message = 'Вы уверены, что хотите удалить этого пользователя? Это действие нельзя отменить.'; break; case 'order': message = 'Вы уверены, что хотите удалить этот заказ? Это действие нельзя отменить.'; break; } document.getElementById('confirmMessage').textContent = message; showModal(confirmModal); } // Функция показа уведомления function showNotification(message, type = 'info') { // Удаляем существующие уведомления const existing = document.querySelector('.notification'); if (existing) existing.remove(); // Создаем новое уведомление const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.innerHTML = ` <div class="notification-content"> <span class="notification-message">${message}</span> <button class="notification-close">×</button> </div> `; document.body.appendChild(notification); // Закрытие по клику на крестик notification.querySelector('.notification-close').addEventListener('click', () => { notification.remove(); }); // Автоматическое закрытие через 3 секунды setTimeout(() => { if (notification.parentNode) { notification.classList.add('closing'); setTimeout(() => { notification.remove(); }, 300); } }, 3000); } // Функция сохранения товара async function saveProduct() { const title = document.getElementById('productTitle').value; const description = document.getElementById('productDescription').value; const price = parseInt(document.getElementById('productPrice').value); const amount = parseInt(document.getElementById('productAmount').value); const categoryId = document.getElementById('productCategory').value; const image = document.getElementById('productImage').value; if (!title || !price || !amount || !categoryId) { document.getElementById('productFormError').textContent = 'Пожалуйста, заполните все обязательные поля'; return; } try { const url = editingProductId ? `/api/lab10/admin/products/${editingProductId}` : '/api/lab10/admin/products'; const method = editingProductId ? 'PUT' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, description, price, amount, categoryId: parseInt(categoryId), image }) }); const result = await response.json(); if (response.ok) { hideModal(productModal); loadProducts(); showNotification(editingProductId ? 'Товар успешно обновлен!' : 'Товар успешно добавлен!', 'success'); } else { document.getElementById('productFormError').textContent = result.error || 'Ошибка сохранения товара'; } } catch (error) { document.getElementById('productFormError').textContent = 'Ошибка сети. Попробуйте позже.'; console.error('Ошибка сохранения товара:', error); } } // Функция сохранения пользователя async function saveUser() { const username = document.getElementById('userUsername').value; const email = document.getElementById('userEmail').value; const password = document.getElementById('userPassword').value; const role = document.getElementById('userRole').value; if (!username || !email || !password || !role) { document.getElementById('userFormError').textContent = 'Пожалуйста, заполните все обязательные поля'; return; } try { const url = editingUserId ? `/api/lab10/admin/users/${editingUserId}` : '/api/lab10/admin/users'; const method = editingUserId ? 'PUT' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, email, password, role }) }); const result = await response.json(); if (response.ok) { hideModal(userModal); loadUsers(); showNotification(editingUserId ? 'Пользователь успешно обновлен!' : 'Пользователь успешно добавлен!', 'success'); } else { document.getElementById('userFormError').textContent = result.error || 'Ошибка сохранения пользователя'; } } catch (error) { document.getElementById('userFormError').textContent = 'Ошибка сети. Попробуйте позже.'; console.error('Ошибка сохранения пользователя:', error); } } // Функция удаления товара async function deleteProduct(productId) { try { const response = await fetch(`/api/lab10/admin/products/${productId}`, { method: 'DELETE' }); const result = await response.json(); if (response.ok) { loadProducts(); showNotification('Товар успешно удален!', 'success'); } else { showNotification(result.error || 'Ошибка удаления товара', 'error'); } } catch (error) { showNotification('Ошибка сети. Попробуйте позже.', 'error'); console.error('Ошибка удаления товара:', error); } } // Функция удаления пользователя async function deleteUser(userId) { try { const response = await fetch(`/api/lab10/admin/users/${userId}`, { method: 'DELETE' }); const result = await response.json(); if (response.ok) { loadUsers(); showNotification('Пользователь успешно удален!', 'success'); } else { showNotification(result.error || 'Ошибка удаления пользователя', 'error'); } } catch (error) { showNotification('Ошибка сети. Попробуйте позже.', 'error'); console.error('Ошибка удаления пользователя:', error); } } // Функция сохранения статуса заказа async function saveOrderStatus() { const orderId = document.getElementById('statusOrderId').value; const status = document.getElementById('orderStatus').value; if (!status) { document.getElementById('statusFormError').textContent = 'Пожалуйста, выберите статус'; return; } try { const response = await fetch(`/api/lab10/admin/orders/${orderId}/status`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ status }) }); const result = await response.json(); if (response.ok) { hideModal(statusModal); loadOrders(orderStatusFilter.value); showNotification('Статус заказа успешно обновлен!', 'success'); } else { document.getElementById('statusFormError').textContent = result.error || 'Ошибка обновления статуса'; } } catch (error) { document.getElementById('statusFormError').textContent = 'Ошибка сети. Попробуйте позже.'; console.error('Ошибка обновления статуса:', error); } } // Обработчики событий tabBtns.forEach(btn => { btn.addEventListener('click', () => { switchTab(btn.dataset.tab); }); }); addProductBtn.addEventListener('click', openAddProductForm); addUserBtn.addEventListener('click', openAddUserForm); closeProductModal.addEventListener('click', () => hideModal(productModal)); closeUserModal.addEventListener('click', () => hideModal(userModal)); closeOrderModal.addEventListener('click', () => hideModal(orderModal)); closeStatusModal.addEventListener('click', () => hideModal(statusModal)); closeConfirmModal.addEventListener('click', () => hideModal(confirmModal)); cancelProductBtn.addEventListener('click', () => hideModal(productModal)); cancelUserBtn.addEventListener('click', () => hideModal(userModal)); cancelStatusBtn.addEventListener('click', () => hideModal(statusModal)); cancelConfirmBtn.addEventListener('click', () => hideModal(confirmModal)); closeOrderDetailsBtn.addEventListener('click', () => hideModal(orderModal)); productForm.addEventListener('submit', (e) => { e.preventDefault(); saveProduct(); }); userForm.addEventListener('submit', (e) => { e.preventDefault(); saveUser(); }); statusForm.addEventListener('submit', (e) => { e.preventDefault(); saveOrderStatus(); }); confirmDeleteBtn.addEventListener('click', () => { switch(currentDeleteType) { case 'product': deleteProduct(currentDeleteId); break; case 'user': deleteUser(currentDeleteId); break; } hideModal(confirmModal); }); // Поиск товаров productSearch.addEventListener('input', (e) => { const query = e.target.value.trim(); loadProducts(query); }); // Поиск пользователей userSearch.addEventListener('input', (e) => { const query = e.target.value.trim(); loadUsers(query); }); // Фильтр по статусу заказов orderStatusFilter.addEventListener('change', (e) => { const status = e.target.value; loadOrders(status); }); // Закрытие модальных окон при клике вне их [productModal, userModal, orderModal, statusModal, confirmModal].forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { hideModal(modal); } }); }); // Инициализация switchTab('products'); });