/
Krouli
/
Project_D
Обзор
Документация
Войти
/
Krouli
/
Project_D
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
dashboard.html
1 457 строк
56 KB
Krouli
upload files
18 дек 2025, 19:16
18 дек 2025, 19:16
63f7ab2
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Личный кабинет - Project D</title> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> /* Стили для карусели */ .carousel-section { margin: 2rem 0; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 2rem; border-radius: 15px; position: relative; } .carousel-container { position: relative; max-width: 800px; margin: 0 auto; overflow: hidden; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); height: 400px; } .carousel-slides { display: flex; transition: transform 0.5s ease; height: 100%; } .carousel-slide { min-width: 100%; position: relative; } .carousel-slide img { width: 100%; height: 100%; object-fit: cover; display: block; } .carousel-slide .slide-caption { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0.5), transparent); color: white; padding: 20px 15px 60px 15px; text-align: center; } .carousel-slide .slide-caption h4 { margin: 0; font-size: 1.4rem; color: #ff5500; font-weight: bold; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8); } .carousel-slide .slide-caption p { margin: 8px 0 0 0; font-size: 1rem; opacity: 0.9; text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); } .carousel-btn { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(255, 85, 0, 0.9); color: white; border: none; width: 50px; height: 50px; border-radius: 50%; cursor: pointer; font-size: 1.5rem; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 10; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); } .carousel-btn:hover { background: rgba(255, 85, 0, 1); transform: translateY(-50%) scale(1.1); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4); } .carousel-prev { left: 20px; } .carousel-next { right: 20px; } .carousel-dots-container { position: absolute; bottom: 20px; left: 0; right: 0; z-index: 10; background: rgba(0, 0, 0, 0.5); padding: 15px 0; border-radius: 0 0 10px 10px; } .carousel-dots { display: flex; justify-content: center; gap: 12px; } .carousel-dot { width: 14px; height: 14px; border-radius: 50%; border: 2px solid white; background: rgba(255, 255, 255, 0.3); cursor: pointer; transition: all 0.3s ease; padding: 0; } .carousel-dot:hover { background: rgba(255, 255, 255, 0.6); } .carousel-dot.active { background: #ff5500; border-color: #ff5500; transform: scale(1.2); box-shadow: 0 0 10px rgba(255, 85, 0, 0.8); } /* Стили для таблицы */ .table-section { background: white; border-radius: 15px; padding: 2rem; margin-top: 2rem; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .data-table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; } .data-table th { background: #f8f9fa; padding: 1rem; text-align: left; font-weight: 600; color: #333; border-bottom: 2px solid #ddd; } .data-table td { padding: 1rem; border-bottom: 1px solid #eee; } .data-table tr:hover { background: #f9f9f9; } .data-table tr:last-child td { border-bottom: none; } .status-badge { padding: 0.4rem 1rem; border-radius: 20px; font-size: 0.9rem; font-weight: 600; display: inline-block; min-width: 100px; text-align: center; } .status-available { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status-sold { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .action-btn { background: none; border: none; cursor: pointer; font-size: 1.2rem; padding: 0.5rem; border-radius: 5px; transition: all 0.3s ease; } .action-view { color: #17a2b8; } .action-view:hover { background: rgba(23, 162, 184, 0.1); } .action-remove { color: #dc3545; } .action-remove:hover { background: rgba(220, 53, 69, 0.1); } .table-pagination { display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; border-top: 1px solid #eee; } .pagination-btn { padding: 0.6rem 1.2rem; background: #ff5500; color: white; border: none; border-radius: 5px; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.3s ease; font-weight: 600; } .pagination-btn:hover:not(:disabled) { background: #ff3300; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(255, 85, 0, 0.3); } .pagination-btn:disabled { opacity: 0.5; cursor: not-allowed; background: #ccc; } .pagination-info { color: #666; font-size: 1rem; font-weight: 500; } /* Карточки профиля */ .profile-card { display: flex; align-items: center; gap: 2rem; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); padding: 2rem; border-radius: 15px; color: white; margin-bottom: 2rem; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } .profile-avatar { width: 100px; height: 100px; border-radius: 50%; background: #ff5500; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; font-weight: bold; box-shadow: 0 5px 15px rgba(255, 85, 0, 0.3); } .profile-info { flex: 1; } .profile-name { margin: 0 0 0.5rem 0; font-size: 2rem; } .profile-role { margin: 0 0 0.5rem 0; color: #ffa366; font-size: 1.1rem; } .profile-date { margin: 0 0 1rem 0; color: #aaa; font-size: 0.9rem; } .profile-btn { background: #ff5500; color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 8px; cursor: pointer; font-size: 1rem; display: flex; align-items: center; gap: 0.5rem; transition: all 0.3s ease; } .profile-btn:hover { background: #ff3300; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(255, 85, 0, 0.4); } /* Графики */ .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem; } .chart-card { background: white; border-radius: 10px; padding: 1.5rem; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); } .chart-title { margin: 0 0 1rem 0; color: #333; font-size: 1.2rem; display: flex; align-items: center; gap: 0.5rem; } .chart-title i { color: #ff5500; } .progress-stats { margin-top: 1rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .progress-stat { text-align: center; padding: 0.5rem; background: #f8f9fa; border-radius: 5px; } .progress-stat span:first-child { display: block; font-size: 0.9rem; color: #666; margin-bottom: 0.3rem; } .progress-value { font-weight: bold; color: #ff5500; font-size: 1.1rem; } /* Лоадер */ .loader { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: #1a1a2e; display: flex; align-items: center; justify-content: center; z-index: 9999; transition: opacity 0.3s ease; } .loader.hidden { opacity: 0; pointer-events: none; } .loader-spinner { width: 50px; height: 50px; border: 5px solid rgba(255, 85, 0, 0.3); border-radius: 50%; border-top-color: #ff5500; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Адаптивность */ @media (max-width: 768px) { .profile-card { flex-direction: column; text-align: center; } .carousel-container { height: 300px; } .charts-grid { grid-template-columns: 1fr; } .data-table { display: block; overflow-x: auto; } .carousel-btn { width: 40px; height: 40px; font-size: 1.2rem; } .carousel-prev { left: 10px; } .carousel-next { right: 10px; } .carousel-slide .slide-caption { padding: 15px 10px 50px 10px; } .carousel-slide .slide-caption h4 { font-size: 1.2rem; } .carousel-slide .slide-caption p { font-size: 0.9rem; } } @media (max-width: 480px) { .profile-card { padding: 1.5rem; } .profile-avatar { width: 80px; height: 80px; font-size: 2rem; } .profile-name { font-size: 1.5rem; } .carousel-container { height: 250px; } } </style> </head> <body> <div class="loader" id="loader"> <div class="loader-spinner"></div> </div> <header> <div class="logo-container"> <img class="logo" src="photos/Project_d_logo.webp" alt="Project D Logo"> </div> <nav> <ul> <li><a href="index.html">Главная</a></li> <li><a href="cars.html">Автомобили</a></li> <li><a href="map.html">Карта</a></li> <li id="nav-auth" style="display: none;"> <a href="contact.html">Войти</a> </li> <li id="nav-user"> <a href="dashboard.html" class="active">Личный кабинет</a> </li> <li id="nav-logout"> <a href="#" id="logout-link">Выход</a> </li> </ul> </nav> </header> <main> <section class="profile-section"> <div class="profile-card"> <div class="profile-avatar" id="user-avatar-container"> <span id="user-avatar">A</span> </div> <div class="profile-info"> <h1 class="profile-name" id="profile-name">Администратор</h1> <p class="profile-role" id="profile-role">Премиум пользователь</p> <p class="profile-date" id="profile-date">Присоединился: 17 ноября 2025</p> <div class="profile-stats" id="profile-stats"> <div class="favorites-count" style="margin-top: 10px; color: #ff5500; font-weight: bold;"> <i class="fas fa-heart"></i> Избранное: <span id="favorites-count">0</span> авто </div> </div> </div> <button class="btn profile-btn" id="edit-profile-btn"> <i class="fas fa-edit"></i> Редактировать профиль </button> </div> </section> <!-- Карусель --> <section class="carousel-section" id="carousel-section"> <h2 class="section-title" style="text-align: center; margin-bottom: 1.5rem; color: white;"> <i class="fas fa-images"></i> Галерея автомобилей </h2> <div class="carousel-container"> <div class="carousel-slides" id="carousel-slides"> <!-- Слайды будут добавлены через JS --> </div> <button class="carousel-btn carousel-prev" id="carousel-prev"> <i class="fas fa-chevron-left"></i> </button> <button class="carousel-btn carousel-next" id="carousel-next"> <i class="fas fa-chevron-right"></i> </button> <div class="carousel-dots-container"> <div class="carousel-dots" id="carousel-dots"></div> </div> </div> </section> <section class="charts-section" style="background: #f8f9fa; padding: 2rem; border-radius: 15px; margin: 2rem 0;"> <h2 class="section-title" style="text-align: center; margin-bottom: 2rem; color: #333;">📈 Аналитика</h2> <div class="charts-grid"> <div class="chart-card"> <h3 class="chart-title"><i class="fas fa-chart-line"></i> Активность за 7 дней</h3> <div style="height: 200px; position: relative;"> <canvas id="activityChart"></canvas> </div> </div> <div class="chart-card"> <h3 class="chart-title"><i class="fas fa-car"></i> Интересующие марки</h3> <div style="height: 200px; position: relative;"> <canvas id="brandsChart"></canvas> </div> </div> <div class="chart-card"> <h3 class="chart-title"><i class="fas fa-shapes"></i> Распределение по типам</h3> <div style="height: 200px; position: relative;"> <canvas id="typesChart"></canvas> </div> </div> <div class="chart-card"> <h3 class="chart-title"><i class="fas fa-tasks"></i> Прогресс месяца</h3> <div style="height: 200px; position: relative;"> <canvas id="progressChart"></canvas> </div> <div class="progress-stats"> <div class="progress-stat"> <span>Цель:</span> <span class="progress-value">10 авто</span> </div> <div class="progress-stat"> <span>Просмотрено:</span> <span class="progress-value">7 авто</span> </div> <div class="progress-stat"> <span>Осталось:</span> <span class="progress-value">3 авто</span> </div> </div> </div> </div> </section> <section class="table-section"> <div class="section-header" style="margin-bottom: 2rem;"> <h2 class="section-title" style="color: #333; margin-bottom: 1rem;"> <i class="fas fa-heart"></i> Мои избранные автомобили </h2> </div> <div class="table-responsive"> <table class="data-table" id="cars-table"> <thead> <tr> <th>Автомобиль</th> <th>Марка</th> <th>Год</th> <th>Цена</th> <th>Тип</th> <th>Статус</th> <th>Добавлено</th> <th>Действия</th> </tr> </thead> <tbody id="cars-table-body"> <tr id="loading-row"> <td colspan="8" style="text-align: center; padding: 3rem;"> <div class="loader-spinner" style="width: 40px; height: 40px; margin: 0 auto;"></div> <p style="margin-top: 1rem; color: #666;">Загрузка избранных автомобилей...</p> </td> </tr> </tbody> </table> </div> <div class="table-pagination"> <button class="btn pagination-btn" id="prev-btn" disabled> <i class="fas fa-chevron-left"></i> Назад </button> <span class="pagination-info"> Страница <span id="current-page">1</span> из <span id="total-pages">1</span> </span> <button class="btn pagination-btn" id="next-btn" disabled> Вперед <i class="fas fa-chevron-right"></i> </button> </div> <div id="no-favorites-message" style="display: none; text-align: center; padding: 3rem; color: #666;"> <i class="fas fa-heart" style="font-size: 3rem; color: #ff5500; margin-bottom: 1rem;"></i> <h3 style="margin-bottom: 1rem; color: #333;">У вас пока нет избранных автомобилей</h3> <p style="margin-bottom: 2rem; max-width: 500px; margin: 0 auto 2rem;">Добавляйте понравившиеся автомобили в избранное, чтобы вернуться к ним позже!</p> <a href="cars.html" class="btn" style="background: #ff5500; color: white; padding: 0.8rem 1.5rem; text-decoration: none; border-radius: 5px; display: inline-flex; align-items: center; gap: 0.5rem;"> <i class="fas fa-car"></i> Перейти в каталог </a> </div> </section> </main> <footer style="background: #1a1a2e; color: white; padding: 2rem; text-align: center; margin-top: 2rem;"> <p>© 2025 Project D. Все права защищены.</p> </footer> <script> // Переменные для хранения экземпляров графиков let activityChart = null; let brandsChart = null; let typesChart = null; let progressChart = null; // Обновление навигации при загрузке document.addEventListener('DOMContentLoaded', function() { updateNavigation(); hideLoader(); loadUserData(); loadFavoritesDashboard(); initDashboardCharts(); initCarousel(); // Инициализация обработчиков событий initEventListeners(); }); function hideLoader() { setTimeout(() => { const loader = document.getElementById('loader'); if (loader) { loader.classList.add('hidden'); } }, 500); } function updateNavigation() { const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true'; const username = localStorage.getItem('username'); const navAuth = document.getElementById('nav-auth'); const navUser = document.getElementById('nav-user'); const navLogout = document.getElementById('nav-logout'); const logoutLink = document.getElementById('logout-link'); if (isAuthenticated && username) { if (navAuth) navAuth.style.display = 'none'; if (navUser) navUser.style.display = 'block'; if (navLogout) navLogout.style.display = 'block'; if (logoutLink) { logoutLink.addEventListener('click', function(e) { e.preventDefault(); handleLogout(); }); } } else { // Если не авторизован, перенаправляем на страницу входа window.location.href = 'contact.html'; } } function loadUserData() { const username = localStorage.getItem('username'); const userRole = localStorage.getItem('userRole') || 'user'; const userId = localStorage.getItem('userId'); if (username) { document.getElementById('profile-name').textContent = username; document.getElementById('profile-role').textContent = userRole === 'admin' ? 'Администратор' : userRole === 'premium' ? 'Премиум пользователь' : 'Пользователь'; } // Загружаем дату регистрации if (userId) { fetch(`http://localhost:3000/api/users/${userId}`) .then(response => response.json()) .then(user => { if (user.created_at) { const date = new Date(user.created_at); const formattedDate = date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }); document.getElementById('profile-date').textContent = `Присоединился: ${formattedDate}`; } }) .catch(error => { console.error('Error loading user data:', error); }); } } async function loadFavoritesDashboard() { const userId = localStorage.getItem('userId'); if (!userId) { showNoFavoritesMessage(); return; } try { // Загружаем избранные автомобили const response = await fetch(`http://localhost:3000/api/favorites/${userId}`); if (!response.ok) throw new Error('Failed to load favorites'); const favorites = await response.json(); // Обновляем счетчик избранного const favoritesCount = document.getElementById('favorites-count'); if (favoritesCount) { favoritesCount.textContent = favorites.length; } // Если нет избранных, показываем сообщение if (favorites.length === 0) { showNoFavoritesMessage(); return; } // Обновляем таблицу updateFavoritesTable(favorites); } catch (error) { console.error('Error loading favorites:', error); showNoFavoritesMessage(); } } function updateFavoritesTable(favorites) { const tableBody = document.getElementById('cars-table-body'); const loadingRow = document.getElementById('loading-row'); if (loadingRow) loadingRow.remove(); if (!favorites || favorites.length === 0) { showNoFavoritesMessage(); return; } let tableHTML = ''; // Сортируем по дате добавления (новые сначала) favorites.sort((a, b) => new Date(b.addedAt) - new Date(a.addedAt)); favorites.forEach((favorite) => { const car = favorite.car; if (!car) return; const addedDate = new Date(favorite.addedAt); const formattedDate = addedDate.toLocaleDateString('ru-RU'); const formattedTime = addedDate.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); tableHTML += ` <tr data-favorite-id="${favorite.id}" data-car-id="${car.id}"> <td> <div style="display: flex; align-items: center; gap: 10px;"> <img src="${car.image}" alt="${car.name}" style="width: 80px; height: 60px; object-fit: cover; border-radius: 5px;" onerror="this.src='photos/default-car.jpg'"> <div> <strong style="color: #333;">${car.name}</strong> <div style="font-size: 0.9rem; color: #666;">${car.description.substring(0, 50)}...</div> </div> </div> </td> <td style="font-weight: 600;">${car.brand.charAt(0).toUpperCase() + car.brand.slice(1)}</td> <td>${car.year}</td> <td style="font-weight: bold; color: #ff5500;">${formatPrice(car.price)} руб</td> <td>${getTypeName(car.type)}</td> <td> <span class="status-badge ${car.isAvailable ? 'status-available' : 'status-sold'}"> ${car.isAvailable ? 'В наличии' : 'Продан'} </span> </td> <td title="Добавлено в ${formattedTime}"> <div style="display: flex; flex-direction: column; gap: 3px;"> <span>${formattedDate}</span> <small style="color: #888; font-size: 0.8rem;">${formattedTime}</small> </div> </td> <td> <div style="display: flex; gap: 8px;"> <button class="action-btn action-view" title="Просмотреть" data-car-id="${car.id}"> <i class="fas fa-eye"></i> </button> <button class="action-btn action-remove" title="Удалить из избранного" data-car-id="${car.id}"> <i class="fas fa-trash"></i> </button> </div> </td> </tr> `; }); tableBody.innerHTML = tableHTML; // Инициализация пагинации initPagination(); // Добавляем обработчики событий document.querySelectorAll('.action-view').forEach(btn => { btn.addEventListener('click', async function() { const carId = this.getAttribute('data-car-id'); try { const response = await fetch(`http://localhost:3000/api/cars/${carId}`); if (!response.ok) throw new Error('Car not found'); const car = await response.json(); showCarDetailsModal(car); } catch (error) { console.error('Error loading car details:', error); showNotification('Не удалось загрузить информацию об автомобиле', 'error'); } }); }); document.querySelectorAll('.action-remove').forEach(btn => { btn.addEventListener('click', async function() { const carId = this.getAttribute('data-car-id'); const row = this.closest('tr'); const carName = row.querySelector('td strong').textContent.trim(); if (confirm(`Вы действительно хотите удалить "${carName}" из избранного?`)) { const userId = localStorage.getItem('userId'); try { const response = await fetch(`http://localhost:3000/api/favorites/${userId}/${carId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { row.remove(); // Обновляем счетчик const favoritesCount = document.getElementById('favorites-count'); if (favoritesCount) { const currentCount = parseInt(favoritesCount.textContent); favoritesCount.textContent = currentCount - 1; } // Показываем уведомление showNotification(`${carName} удален из избранного`, 'success'); // Проверяем, остались ли еще избранные const remainingRows = tableBody.querySelectorAll('tr'); if (remainingRows.length === 0) { showNoFavoritesMessage(); } } else { showNotification('Ошибка при удалении из избранного', 'error'); } } catch (error) { console.error('Error removing favorite:', error); showNotification('Ошибка при удалении из избранного', 'error'); } } }); }); } function initPagination() { const rowsPerPage = 5; const rows = Array.from(document.querySelectorAll('#cars-table-body tr')); const totalPages = Math.ceil(rows.length / rowsPerPage); let currentPage = 1; function showPage(page) { const start = (page - 1) * rowsPerPage; const end = start + rowsPerPage; rows.forEach((row, index) => { row.style.display = (index >= start && index < end) ? '' : 'none'; }); document.getElementById('current-page').textContent = page; document.getElementById('total-pages').textContent = totalPages; document.getElementById('prev-btn').disabled = page === 1; document.getElementById('next-btn').disabled = page === totalPages; } document.getElementById('prev-btn').addEventListener('click', () => { if (currentPage > 1) { currentPage--; showPage(currentPage); } }); document.getElementById('next-btn').addEventListener('click', () => { if (currentPage < totalPages) { currentPage++; showPage(currentPage); } }); showPage(1); } function initCarousel() { const carouselImages = [ { src: 'photos/toytgt86055.jpg', title: 'Toyota GR86', desc: 'Легендарное спортивное купе с задним приводом' }, { src: 'photos/nismo.jpg', title: 'Nissan GT-R Nismo', desc: 'Легендарный Godzilla с двигателем V6' }, { src: 'photos/fd.webp', title: 'Mazda RX-7 FD', desc: 'Культовый спорткар с роторным двигателем' }, { src: 'photos/toyotasupra.jpg', title: 'Toyota Supra MK4', desc: 'Икона тюнинга 90-х годов' }, { src: 'photos/s2000.jpg', title: 'Honda S2000', desc: 'Родстер с высокооборотистым двигателем' } ]; const slidesContainer = document.getElementById('carousel-slides'); const dotsContainer = document.getElementById('carousel-dots'); // Создаем слайды carouselImages.forEach((img, index) => { const slide = document.createElement('div'); slide.className = 'carousel-slide'; slide.innerHTML = ` <img src="${img.src}" alt="${img.title}" onerror="this.src='photos/default-car.jpg'"> <div class="slide-caption"> <h4>${img.title}</h4> <p>${img.desc}</p> </div> `; slidesContainer.appendChild(slide); // Создаем точки навигации const dot = document.createElement('button'); dot.className = 'carousel-dot'; if (index === 0) dot.classList.add('active'); dot.addEventListener('click', () => goToSlide(index)); dotsContainer.appendChild(dot); }); let currentSlide = 0; const totalSlides = carouselImages.length; function goToSlide(index) { currentSlide = index; slidesContainer.style.transform = `translateX(-${index * 100}%)`; // Обновляем точки document.querySelectorAll('.carousel-dot').forEach((dot, i) => { dot.classList.toggle('active', i === index); }); } function nextSlide() { currentSlide = (currentSlide + 1) % totalSlides; goToSlide(currentSlide); } function prevSlide() { currentSlide = (currentSlide - 1 + totalSlides) % totalSlides; goToSlide(currentSlide); } // Обработчики кнопок document.getElementById('carousel-prev').addEventListener('click', prevSlide); document.getElementById('carousel-next').addEventListener('click', nextSlide); // Автоматическое перелистывание let slideInterval = setInterval(nextSlide, 5000); // Останавливаем автопрокрутку при наведении const carouselContainer = document.querySelector('.carousel-container'); carouselContainer.addEventListener('mouseenter', () => { clearInterval(slideInterval); }); carouselContainer.addEventListener('mouseleave', () => { slideInterval = setInterval(nextSlide, 5000); }); } function initDashboardCharts() { // Уничтожаем предыдущие графики, если они существуют if (activityChart) activityChart.destroy(); if (brandsChart) brandsChart.destroy(); if (typesChart) typesChart.destroy(); if (progressChart) progressChart.destroy(); // Активность за 7 дней const activityCtx = document.getElementById('activityChart')?.getContext('2d'); if (activityCtx && typeof Chart !== 'undefined') { activityChart = new Chart(activityCtx, { type: 'line', data: { labels: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'], datasets: [{ label: 'Просмотры автомобилей', data: [12, 19, 8, 15, 22, 18, 25], borderColor: '#ff5500', backgroundColor: 'rgba(255, 85, 0, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointBackgroundColor: '#ff5500', pointBorderColor: '#fff', pointBorderWidth: 2, pointRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: '#e1e5e9' }, ticks: { color: '#666' } }, x: { grid: { color: '#e1e5e9' }, ticks: { color: '#666' } } } } }); } // Марки автомобилей const brandsCtx = document.getElementById('brandsChart')?.getContext('2d'); if (brandsCtx) { brandsChart = new Chart(brandsCtx, { type: 'bar', data: { labels: ['Toyota', 'Nissan', 'Mazda', 'Honda', 'Subaru', 'Mitsubishi'], datasets: [{ label: 'Количество просмотров', data: [42, 38, 25, 22, 18, 15], backgroundColor: '#ff5500', borderRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: '#e1e5e9' }, ticks: { color: '#666' } }, x: { grid: { color: '#e1e5e9' }, ticks: { color: '#666' } } } } }); } // Типы кузова const typesCtx = document.getElementById('typesChart')?.getContext('2d'); if (typesCtx) { typesChart = new Chart(typesCtx, { type: 'doughnut', data: { labels: ['Купе', 'Седан', 'Хэтчбек', 'Внедорожник'], datasets: [{ data: [45, 25, 15, 15], backgroundColor: ['#ff5500', '#ff8c42', '#ffa366', '#ffb88c'], borderWidth: 2, borderColor: '#fff' }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { padding: 20, color: '#666' } } } } }); } // Прогресс месяца const progressCtx = document.getElementById('progressChart')?.getContext('2d'); if (progressCtx) { progressChart = new Chart(progressCtx, { type: 'doughnut', data: { labels: ['Выполнено', 'Осталось'], datasets: [{ data: [70, 30], backgroundColor: ['#ff5500', '#e1e5e9'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { display: false } } }, plugins: [{ id: 'centerText', afterDraw: (chart) => { const { ctx, chartArea: { width, height } } = chart; ctx.save(); ctx.font = 'bold 24px Arial'; ctx.fillStyle = '#333'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('70%', width / 2, height / 2); ctx.restore(); } }] }); } } function initEventListeners() { // Кнопка редактирования профиля document.getElementById('edit-profile-btn')?.addEventListener('click', function() { showNotification('Функция редактирования профиля в разработке', 'info'); }); } function showCarDetailsModal(car) { // Создаем модальное окно const modalHTML = ` <div class="modal-overlay" id="car-details-modal" style=" position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 1rem; "> <div class="modal-content" style=" background: white; border-radius: 15px; padding: 2rem; max-width: 800px; width: 100%; max-height: 90vh; overflow-y: auto; position: relative; "> <button class="modal-close" id="modal-close" style=" position: absolute; top: 1rem; right: 1rem; background: none; border: none; font-size: 2rem; cursor: pointer; color: #666; ">×</button> <h2 style="color: #333; margin-bottom: 1.5rem;">${car.name}</h2> <div style="display: flex; gap: 2rem; margin-bottom: 1.5rem; flex-wrap: wrap;"> <div style="flex: 1; min-width: 300px;"> <img src="${car.image}" alt="${car.name}" style="width: 100%; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);" onerror="this.src='photos/default-car.jpg'"> </div> <div style="flex: 1; min-width: 300px;"> <h3 style="color: #333; margin-bottom: 1rem; font-size: 1.3rem;">Характеристики</h3> <div style="display: flex; flex-direction: column; gap: 0.8rem;"> <div style="display: flex; justify-content: space-between; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;"> <strong>Марка:</strong> <span>${car.brand.charAt(0).toUpperCase() + car.brand.slice(1)}</span> </div> <div style="display: flex; justify-content: space-between; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;"> <strong>Год выпуска:</strong> <span>${car.year}</span> </div> <div style="display: flex; justify-content: space-between; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;"> <strong>Тип кузова:</strong> <span>${getTypeName(car.type)}</span> </div> <div style="display: flex; justify-content: space-between; border-bottom: 1px solid #eee; padding-bottom: 0.5rem;"> <strong>Статус:</strong> <span style="color: ${car.isAvailable ? '#27ae60' : '#e74c3c'}; font-weight: bold;"> ${car.isAvailable ? 'В наличии' : 'Продан'} </span> </div> <div style="display: flex; justify-content: space-between; padding-top: 1rem;"> <strong style="font-size: 1.2rem;">Цена:</strong> <span style="font-size: 1.4rem; color: #ff5500; font-weight: bold;"> ${formatPrice(car.price)} руб </span> </div> </div> </div> </div> <div style="margin-bottom: 1.5rem;"> <h3 style="color: #333; margin-bottom: 0.8rem;">Описание</h3> <p style="color: #555; line-height: 1.6;">${car.description}</p> </div> <div style="margin-bottom: 2rem;"> <h3 style="color: #333; margin-bottom: 0.8rem;">Технические характеристики</h3> <p style="color: #555; background: #f8f9fa; padding: 1rem; border-radius: 8px;">${car.specs}</p> </div> <button class="btn" id="close-modal-btn" style=" width: 100%; padding: 1rem; background: #ff5500; color: white; border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer; font-weight: bold; ">Закрыть</button> </div> </div> `; document.body.insertAdjacentHTML('beforeend', modalHTML); // Обработчики закрытия модального окна document.getElementById('modal-close').addEventListener('click', () => { document.getElementById('car-details-modal').remove(); }); document.getElementById('close-modal-btn').addEventListener('click', () => { document.getElementById('car-details-modal').remove(); }); document.getElementById('car-details-modal').addEventListener('click', (e) => { if (e.target === document.getElementById('car-details-modal')) { document.getElementById('car-details-modal').remove(); } }); } function showNoFavoritesMessage() { const tableBody = document.getElementById('cars-table-body'); const table = document.getElementById('cars-table'); const pagination = document.querySelector('.table-pagination'); const noFavoritesMessage = document.getElementById('no-favorites-message'); if (tableBody) tableBody.innerHTML = ''; if (table) table.style.display = 'none'; if (pagination) pagination.style.display = 'none'; if (noFavoritesMessage) noFavoritesMessage.style.display = 'block'; } function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.style.cssText = ` position: fixed; top: 100px; right: 20px; background: white; padding: 1rem 1.5rem; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); transform: translateX(400px); opacity: 0; transition: all 0.3s ease; z-index: 10000; max-width: 350px; border-left: 4px solid ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : '#17a2b8'}; `; notification.innerHTML = ` <div style="display: flex; align-items: center; justify-content: space-between; gap: 1rem;"> <span style="flex: 1; color: #333; font-weight: 500;">${message}</span> <button style="background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #999; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.3s ease;" onclick="this.parentElement.parentElement.remove()"> × </button> </div> `; document.body.appendChild(notification); setTimeout(() => { notification.style.transform = 'translateX(0)'; notification.style.opacity = '1'; }, 10); setTimeout(() => { notification.style.transform = 'translateX(400px)'; notification.style.opacity = '0'; setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, 5000); } function formatPrice(price) { return price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); } function getTypeName(type) { const types = { 'coupe': 'Купе', 'sedan': 'Седан', 'hatchback': 'Хэтчбек', 'suv': 'Внедорожник' }; return types[type] || type; } function handleLogout() { if (confirm('Вы уверены, что хотите выйти?')) { localStorage.removeItem('isAuthenticated'); localStorage.removeItem('username'); localStorage.removeItem('userEmail'); localStorage.removeItem('userPhone'); localStorage.removeItem('userId'); localStorage.removeItem('userRole'); localStorage.removeItem('user'); localStorage.removeItem('project_d_jwt_token'); showNotification('Вы вышли из системы', 'success'); setTimeout(() => { window.location.href = 'index.html'; }, 1000); } } </script> </body> </html>