/
Rayven2035
/
Web
Обзор
Документация
Войти
/
Rayven2035
/
Web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
profile.html
701 строка
46 KB
Rayven2035
create: profile.html, README.md, registration.html, Web-приложение.html, Мобильное приложение.html, Пользовательское_соглашение.html, Презентация сайта (deepseek).html
25 май 2026, 10:13
Верифицирован
25 май 2026, 10:13
c5d93ff
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <title>Логисмарт — Профиль пользователя</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;400;500;600;700;800&display=swap" rel="stylesheet"> <style> * { font-family: 'Inter', sans-serif; } body { background: #f1f5f9; } .profile-card { background: white; border-radius: 1.5rem; transition: all 0.2s ease; } .input-field { transition: all 0.2s ease; border: 1px solid #e2e8f0; } .input-field:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1); outline: none; } .btn-primary { background: linear-gradient(95deg, #2563eb, #1e40af); transition: all 0.2s; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -5px rgba(37,99,235,0.4); } .vehicle-card { transition: all 0.2s; } .vehicle-card:hover { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); } .subscription-plan { cursor: pointer; transition: all 0.2s; border: 2px solid transparent; } .subscription-plan.active { border-color: #3b82f6; background: #eff6ff; } .subscription-plan:hover:not(.active) { border-color: #cbd5e1; background: #f8fafc; } .tab-btn.active { background: #3b82f6; color: white; } .status-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.7rem; font-weight: 500; } .filter-btn.active { background: #3b82f6; color: white; border-color: #3b82f6; } .modal { transition: all 0.3s ease; } .modal.active { display: flex; } .toast { animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .support-link:hover { transform: translateX(4px); } </style> </head> <body class="p-4 md:p-6"> <div class="max-w-6xl mx-auto"> <!-- Шапка профиля --> <div class="mb-6"> <div class="flex items-center justify-between flex-wrap gap-4"> <div class="flex items-center gap-3"> <div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-2xl flex items-center justify-center text-white text-xl font-bold shadow-lg"> <i class="fas fa-user"></i> </div> <div> <h1 class="text-2xl font-bold text-gray-800">Личный кабинет</h1> <p class="text-sm text-gray-500">Управление аккаунтом и автопарком</p> </div> </div> <a href="index.html" class="text-gray-500 hover:text-gray-700 flex items-center gap-2"> <i class="fas fa-arrow-left"></i> Вернуться в приложение </a> </div> </div> <!-- Вкладки навигации --> <div class="flex gap-2 mb-6 border-b border-gray-200 pb-2 overflow-x-auto"> <button data-tab="profile" class="tab-btn px-5 py-2 rounded-xl font-medium transition-all flex items-center gap-2 bg-gray-100 text-gray-700"> <i class="fas fa-user-circle"></i> Личные данные </button> <button data-tab="fleet" class="tab-btn px-5 py-2 rounded-xl font-medium transition-all flex items-center gap-2 bg-gray-100 text-gray-700"> <i class="fas fa-truck"></i> Автопарк </button> <button data-tab="subscription" class="tab-btn px-5 py-2 rounded-xl font-medium transition-all flex items-center gap-2 bg-gray-100 text-gray-700"> <i class="fas fa-crown"></i> Подписка </button> <button data-tab="security" class="tab-btn px-5 py-2 rounded-xl font-medium transition-all flex items-center gap-2 bg-gray-100 text-gray-700"> <i class="fas fa-shield-alt"></i> Безопасность </button> <button data-tab="support" class="tab-btn px-5 py-2 rounded-xl font-medium transition-all flex items-center gap-2 bg-gray-100 text-gray-700"> <i class="fas fa-headset"></i> Поддержка </button> </div> <!-- Контент вкладок --> <div id="tabContent"></div> </div> <!-- Модальное окно для добавления/редактирования ТС --> <div id="vehicleModal" class="modal fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50 hidden" style="backdrop-filter: blur(4px);"> <div class="bg-white rounded-2xl max-w-md w-full shadow-2xl transform transition-all"> <div class="flex justify-between items-center p-5 border-b border-gray-100"> <h3 id="modalTitle" class="text-xl font-bold text-gray-800">Добавление ТС</h3> <button id="closeModalBtn" class="text-gray-400 hover:text-gray-600 text-xl">×</button> </div> <div class="p-5 space-y-4"> <input type="hidden" id="editVehicleId"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Госномер *</label> <input type="text" id="vehiclePlate" placeholder="А777АА 777" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> <div class="grid grid-cols-2 gap-3"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Марка</label> <input type="text" id="vehicleBrand" placeholder="Mercedes-Benz" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Модель</label> <input type="text" id="vehicleModel" placeholder="Actros" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> </div> <div class="grid grid-cols-2 gap-3"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Год выпуска</label> <input type="number" id="vehicleYear" placeholder="2022" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Грузоподъёмность (т) *</label> <input type="number" id="vehicleCapacity" placeholder="20" step="0.5" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Водитель</label> <input type="text" id="vehicleDriver" placeholder="Имя водителя" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Статус</label> <select id="vehicleStatus" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"> <option value="active">Активен</option> <option value="maintenance">На ремонте</option> <option value="inactive">В отстое</option> </select> </div> <div class="bg-blue-50 rounded-xl p-3 text-xs text-blue-700"> <i class="fas fa-info-circle mr-1"></i> Грузоподъёмность определяет категорию: до 10т — лёгкие, 11-20т — средние, 21+т — тяжёлые </div> </div> <div class="flex gap-3 p-5 border-t border-gray-100"> <button id="saveVehicleBtn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-xl font-medium transition"> <i class="fas fa-save mr-1"></i> Сохранить </button> <button id="cancelModalBtn" class="flex-1 border border-gray-300 hover:bg-gray-50 py-2.5 rounded-xl font-medium transition"> Отмена </button> </div> </div> </div> <!-- Toast контейнер --> <div id="toastContainer" class="fixed bottom-4 right-4 z-50 space-y-2"></div> <script> // ========== МОДЕЛЬ ДАННЫХ ПОЛЬЗОВАТЕЛЯ ========== let userData = { id: 'user_123', name: 'Алексей Иванов', email: 'alexey@logismart.ru', phone: '+7 (999) 123-45-67', company: 'ООО "Логистик Про"', inn: '771234567890', address: 'г. Москва, ул. Тверская, д. 15', createdAt: '2024-01-15', subscription: { plan: 'business', status: 'active', expiresAt: '2025-01-15', autoRenew: true } }; // ========== АВТОПАРК ========== let vehicles = [ { id: 'veh_1', plate: 'М777АА', brand: 'Mercedes-Benz', model: 'Actros 1845', year: 2022, capacity: 20, status: 'active', driver: 'Алексей Смирнов' }, { id: 'veh_2', plate: 'О123ВВ', brand: 'Volvo', model: 'FH 500', year: 2021, capacity: 22, status: 'active', driver: 'Дмитрий Козлов' }, { id: 'veh_3', plate: 'Н456СС', brand: 'Scania', model: 'R450', year: 2023, capacity: 19, status: 'maintenance', driver: 'Иван Петров' }, { id: 'veh_4', plate: 'Е789КК', brand: 'MAN', model: 'TGX 18.480', year: 2020, capacity: 8.5, status: 'active', driver: 'Сергей Михайлов' }, { id: 'veh_5', plate: 'А123ВС', brand: 'ГАЗ', model: 'Газель Next', year: 2023, capacity: 3.5, status: 'active', driver: 'Андрей Новиков' }, { id: 'veh_6', plate: 'К999ХХ', brand: 'Volvo', model: 'FH 750', year: 2024, capacity: 28, status: 'active', driver: 'Владимир Соколов' } ]; // Планы подписки const subscriptionPlans = { free: { name: 'Бесплатный', price: '0 ₽', priceMonthly: '0 ₽', features: ['До 10 заказов в месяц', 'Базовая оптимизация', 'Веб-интерфейс', 'Email поддержка'], limit: 10 }, business: { name: 'Бизнес', price: '1 990 ₽', priceMonthly: '1 990 ₽/мес', features: ['Неограниченные заказы', 'AI-динамическая маршрутизация', 'Мобильное приложение', 'Приоритетная поддержка 24/7', 'API доступ', 'Аналитика и отчёты'], limit: Infinity }, corporate: { name: 'Корпоративный', price: 'Индивидуально', priceMonthly: 'от 15 000 ₽/мес', features: ['White-label решение', 'SLA 99.9%', 'Интеграция с 1С/ERP', 'Выделенный AI-инженер', 'Обучение персонала', 'Персональный менеджер'], limit: Infinity } }; let currentTab = 'profile'; let currentFilter = 'all'; let currentEditId = null; // ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ========== function showToast(message, type = 'success') { const container = document.getElementById('toastContainer'); const toast = document.createElement('div'); const icons = { success: '<i class="fas fa-check-circle"></i>', error: '<i class="fas fa-exclamation-circle"></i>', warning: '<i class="fas fa-triangle-exclamation"></i>', info: '<i class="fas fa-info-circle"></i>' }; const bgColors = { success: 'bg-green-500', error: 'bg-red-500', warning: 'bg-amber-500', info: 'bg-blue-500' }; toast.className = `${bgColors[type]} text-white px-5 py-3 rounded-xl shadow-lg flex items-center gap-2 text-sm toast`; toast.innerHTML = `${icons[type]} ${message}`; container.appendChild(toast); setTimeout(() => toast.remove(), 3000); } function saveUserData() { localStorage.setItem('logismart_user_data', JSON.stringify(userData)); } function saveVehicles() { localStorage.setItem('logismart_vehicles', JSON.stringify(vehicles)); } function loadUserData() { const saved = localStorage.getItem('logismart_user_data'); if (saved) userData = JSON.parse(saved); const savedVehicles = localStorage.getItem('logismart_vehicles'); if (savedVehicles) vehicles = JSON.parse(savedVehicles); } // ========== ФУНКЦИИ АВТОПАРКА ========== function getCapacityCategory(capacity) { if (capacity <= 10) return 'light'; if (capacity <= 20) return 'medium'; return 'heavy'; } function getFilteredVehicles() { if (currentFilter === 'all') return vehicles; return vehicles.filter(v => getCapacityCategory(v.capacity) === currentFilter); } function updateFleetStats() { const total = vehicles.length; const active = vehicles.filter(v => v.status === 'active').length; const maintenance = vehicles.filter(v => v.status === 'maintenance').length; const totalCapacity = vehicles.reduce((sum, v) => sum + v.capacity, 0); const statsEl = document.getElementById('fleetStats'); if (statsEl) { statsEl.innerHTML = ` <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"> <div class="bg-white rounded-xl p-4 shadow-sm text-center"> <i class="fas fa-truck text-blue-500 text-2xl mb-1"></i> <p class="text-2xl font-bold">${total}</p> <p class="text-xs text-gray-500">Всего ТС</p> </div> <div class="bg-white rounded-xl p-4 shadow-sm text-center"> <i class="fas fa-check-circle text-green-500 text-2xl mb-1"></i> <p class="text-2xl font-bold">${active}</p> <p class="text-xs text-gray-500">Активных</p> </div> <div class="bg-white rounded-xl p-4 shadow-sm text-center"> <i class="fas fa-wrench text-amber-500 text-2xl mb-1"></i> <p class="text-2xl font-bold">${maintenance}</p> <p class="text-xs text-gray-500">На ремонте</p> </div> <div class="bg-white rounded-xl p-4 shadow-sm text-center"> <i class="fas fa-weight-hanging text-purple-500 text-2xl mb-1"></i> <p class="text-2xl font-bold">${Math.round(totalCapacity)}</p> <p class="text-xs text-gray-500">Общая тоннаж (т)</p> </div> </div> `; } } function renderFleet() { const filtered = getFilteredVehicles(); const categoryLabels = { light: 'Лёгкий', medium: 'Средний', heavy: 'Тяжёлый' }; const categoryColors = { light: 'bg-green-100 text-green-700', medium: 'bg-blue-100 text-blue-700', heavy: 'bg-purple-100 text-purple-700' }; const statusColors = { active: 'bg-green-100 text-green-700', maintenance: 'bg-amber-100 text-amber-700', inactive: 'bg-gray-100 text-gray-600' }; const statusLabels = { active: 'Активен', maintenance: 'На ремонте', inactive: 'В отстое' }; let vehiclesHtml = ''; if (filtered.length === 0) { vehiclesHtml = ` <div class="col-span-full bg-white rounded-xl p-12 text-center"> <i class="fas fa-truck-empty text-gray-300 text-5xl mb-3"></i> <p class="text-gray-500">Нет автомобилей в этой категории</p> <button id="emptyAddBtn" class="mt-3 text-blue-600 hover:text-blue-700">+ Добавить ТС</button> </div> `; } else { vehiclesHtml = filtered.map(vehicle => ` <div class="vehicle-card bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100"> <div class="p-5"> <div class="flex justify-between items-start mb-3"> <div class="flex items-center gap-2"> <i class="fas fa-truck text-blue-500 text-xl"></i> <span class="font-bold text-gray-800 text-lg">${vehicle.plate}</span> </div> <span class="status-badge ${statusColors[vehicle.status]}"> <i class="fas ${vehicle.status === 'active' ? 'fa-circle' : vehicle.status === 'maintenance' ? 'fa-wrench' : 'fa-pause'} text-[8px]"></i> ${statusLabels[vehicle.status]} </span> </div> <p class="text-gray-700 font-medium">${vehicle.brand} ${vehicle.model}</p> <div class="flex flex-wrap gap-3 mt-3 text-sm"> <span class="text-gray-500"><i class="far fa-calendar-alt mr-1"></i>${vehicle.year}</span> <span class="text-gray-500"><i class="fas fa-weight-hanging mr-1"></i>${vehicle.capacity} т</span> <span class="status-badge ${categoryColors[getCapacityCategory(vehicle.capacity)]} py-0.5 px-2 text-xs">${categoryLabels[getCapacityCategory(vehicle.capacity)]}</span> </div> <div class="mt-3 pt-3 border-t border-gray-100 flex justify-between items-center"> <span class="text-sm text-gray-600"><i class="fas fa-user mr-1"></i>${vehicle.driver || 'Не назначен'}</span> <div class="flex gap-2"> <button class="edit-vehicle-btn text-blue-600 hover:text-blue-700 text-sm px-2 py-1 rounded-lg transition" data-id="${vehicle.id}"> <i class="fas fa-edit"></i> Ред. </button> <button class="delete-vehicle-btn text-red-500 hover:text-red-600 text-sm px-2 py-1 rounded-lg transition" data-id="${vehicle.id}"> <i class="fas fa-trash"></i> Удал. </button> </div> </div> </div> </div> `).join(''); } return ` <div class="space-y-6"> <div id="fleetStats"></div> <div class="bg-white rounded-xl p-4 shadow-sm"> <div class="flex justify-between items-center flex-wrap gap-3 mb-4"> <p class="text-sm font-medium text-gray-700 flex items-center gap-2"> <i class="fas fa-filter text-blue-500"></i> Фильтр по грузоподъёмности </p> <button id="addVehicleBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl text-sm font-medium flex items-center gap-2"> <i class="fas fa-plus"></i> Добавить ТС </button> </div> <div class="flex flex-wrap gap-3 mb-4"> <button data-filter="light" class="filter-btn px-4 py-1.5 rounded-lg text-sm font-medium border border-gray-200 bg-gray-50 text-gray-700 transition-all"> <i class="fas fa-truck-ramp-box mr-1"></i> Лёгкие (3.5 - 10 т) </button> <button data-filter="medium" class="filter-btn px-4 py-1.5 rounded-lg text-sm font-medium border border-gray-200 bg-gray-50 text-gray-700 transition-all"> <i class="fas fa-truck-moving mr-1"></i> Средние (11 - 20 т) </button> <button data-filter="heavy" class="filter-btn px-4 py-1.5 rounded-lg text-sm font-medium border border-gray-200 bg-gray-50 text-gray-700 transition-all"> <i class="fas fa-truck mr-1"></i> Тяжёлые (21+ т) </button> <button data-filter="all" class="filter-btn active px-4 py-1.5 rounded-lg text-sm font-medium border border-gray-200 transition-all"> <i class="fas fa-list mr-1"></i> Все ТС </button> </div> </div> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-5" id="vehiclesGrid"> ${vehiclesHtml} </div> </div> `; } function openAddModal() { currentEditId = null; document.getElementById('modalTitle').textContent = 'Добавление ТС'; document.getElementById('editVehicleId').value = ''; document.getElementById('vehiclePlate').value = ''; document.getElementById('vehicleBrand').value = ''; document.getElementById('vehicleModel').value = ''; document.getElementById('vehicleYear').value = new Date().getFullYear(); document.getElementById('vehicleCapacity').value = ''; document.getElementById('vehicleDriver').value = ''; document.getElementById('vehicleStatus').value = 'active'; document.getElementById('vehicleModal').classList.add('active'); document.getElementById('vehicleModal').style.display = 'flex'; } function openEditModal(vehicleId) { const vehicle = vehicles.find(v => v.id === vehicleId); if (!vehicle) return; currentEditId = vehicleId; document.getElementById('modalTitle').textContent = 'Редактирование ТС'; document.getElementById('editVehicleId').value = vehicle.id; document.getElementById('vehiclePlate').value = vehicle.plate; document.getElementById('vehicleBrand').value = vehicle.brand; document.getElementById('vehicleModel').value = vehicle.model; document.getElementById('vehicleYear').value = vehicle.year; document.getElementById('vehicleCapacity').value = vehicle.capacity; document.getElementById('vehicleDriver').value = vehicle.driver || ''; document.getElementById('vehicleStatus').value = vehicle.status; document.getElementById('vehicleModal').classList.add('active'); document.getElementById('vehicleModal').style.display = 'flex'; } function saveVehicle() { const plate = document.getElementById('vehiclePlate').value.trim().toUpperCase(); const brand = document.getElementById('vehicleBrand').value.trim(); const model = document.getElementById('vehicleModel').value.trim(); const year = parseInt(document.getElementById('vehicleYear').value); const capacity = parseFloat(document.getElementById('vehicleCapacity').value); const driver = document.getElementById('vehicleDriver').value.trim(); const status = document.getElementById('vehicleStatus').value; if (!plate) { showToast('Введите госномер', 'error'); return; } if (!brand) { showToast('Введите марку автомобиля', 'error'); return; } if (!model) { showToast('Введите модель автомобиля', 'error'); return; } if (!year || year < 1990 || year > new Date().getFullYear() + 1) { showToast('Введите корректный год выпуска', 'error'); return; } if (!capacity || capacity < 0.5) { showToast('Введите корректную грузоподъёмность', 'error'); return; } if (!currentEditId) { if (vehicles.find(v => v.plate === plate)) { showToast('ТС с таким госномером уже существует', 'error'); return; } const newVehicle = { id: 'veh_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6), plate, brand, model, year, capacity, driver, status }; vehicles.push(newVehicle); showToast(`ТС ${plate} добавлено`, 'success'); } else { const index = vehicles.findIndex(v => v.id === currentEditId); if (index !== -1) { vehicles[index] = { ...vehicles[index], plate, brand, model, year, capacity, driver, status }; showToast(`ТС ${plate} обновлено`, 'success'); } } saveVehicles(); closeModal(); loadTab('fleet'); } function deleteVehicle(vehicleId) { const vehicle = vehicles.find(v => v.id === vehicleId); if (confirm(`Удалить ТС ${vehicle?.plate}?`)) { vehicles = vehicles.filter(v => v.id !== vehicleId); saveVehicles(); showToast(`ТС ${vehicle?.plate} удалено`, 'info'); loadTab('fleet'); } } function closeModal() { document.getElementById('vehicleModal').classList.remove('active'); document.getElementById('vehicleModal').style.display = 'none'; currentEditId = null; } function setFilter(filter) { currentFilter = filter; loadTab('fleet'); } // ========== ОСТАЛЬНЫЕ ВКЛАДКИ ========== function renderProfile() { return ` <div class="profile-card p-6 shadow-sm"> <h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-2"><i class="fas fa-user-circle text-blue-500"></i> Личная информация</h2> <div class="grid md:grid-cols-2 gap-6"> <div><label class="block text-sm font-medium text-gray-700 mb-2">Имя и фамилия</label><input type="text" id="profileName" value="${userData.name}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Email</label><input type="email" id="profileEmail" value="${userData.email}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Телефон</label><input type="tel" id="profilePhone" value="${userData.phone}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Компания</label><input type="text" id="profileCompany" value="${userData.company}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">ИНН</label><input type="text" id="profileInn" value="${userData.inn}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Юридический адрес</label><input type="text" id="profileAddress" value="${userData.address}" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></div> </div> <div class="mt-6 flex justify-end"><button id="saveProfileBtn" class="btn-primary text-white px-6 py-2.5 rounded-xl font-medium flex items-center gap-2"><i class="fas fa-save"></i> Сохранить изменения</button></div> <div class="mt-6 pt-6 border-t border-gray-100"><p class="text-sm text-gray-500"><i class="fas fa-calendar-alt"></i> Аккаунт создан: ${new Date(userData.createdAt).toLocaleDateString('ru-RU')}</p></div> </div> `; } function renderSubscription() { const currentPlan = subscriptionPlans[userData.subscription.plan]; return ` <div class="space-y-6"> <div class="profile-card p-6 shadow-sm bg-gradient-to-r from-blue-50 to-indigo-50"> <div class="flex justify-between items-start flex-wrap gap-4"> <div><p class="text-sm text-gray-500">Текущий тариф</p><h2 class="text-2xl font-bold">${currentPlan.name}</h2><p class="text-sm">${currentPlan.priceMonthly}</p><p class="text-xs text-gray-500 mt-2"><i class="fas fa-calendar"></i> Действует до: ${new Date(userData.subscription.expiresAt).toLocaleDateString('ru-RU')}</p></div> <div class="flex gap-3"><button id="toggleAutoRenewBtn" class="px-4 py-2 rounded-xl text-sm font-medium ${userData.subscription.autoRenew ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'}"><i class="fas ${userData.subscription.autoRenew ? 'fa-toggle-on' : 'fa-toggle-off'} mr-1"></i> Автопродление ${userData.subscription.autoRenew ? 'вкл' : 'выкл'}</button>${userData.subscription.plan !== 'free' ? '<button id="cancelSubscriptionBtn" class="px-4 py-2 rounded-xl text-sm font-medium bg-red-100 text-red-600 hover:bg-red-200 transition"><i class="fas fa-ban"></i> Отключить подписку</button>' : ''}</div> </div> </div> <h2 class="text-xl font-bold text-gray-800 flex items-center gap-2"><i class="fas fa-gem text-blue-500"></i> Выберите тариф</h2> <div class="grid md:grid-cols-3 gap-5"> ${Object.entries(subscriptionPlans).map(([key, plan]) => `<div class="subscription-plan rounded-xl p-5 ${userData.subscription.plan === key ? 'active bg-blue-50' : 'bg-white shadow-sm'}" data-plan="${key}"><h3 class="text-lg font-bold">${plan.name}</h3><p class="text-2xl font-bold mt-2">${plan.price}</p><p class="text-xs text-gray-500">${plan.priceMonthly !== plan.price ? plan.priceMonthly : ''}</p><ul class="mt-4 space-y-2 text-sm">${plan.features.map(f => `<li class="flex items-center gap-2"><i class="fas fa-check text-green-500 text-xs"></i> ${f}</li>`).join('')}</ul><button class="select-plan-btn w-full mt-5 py-2 rounded-xl font-medium transition ${userData.subscription.plan === key ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}" data-plan="${key}">${userData.subscription.plan === key ? 'Текущий план' : 'Выбрать'}</button></div>`).join('')} </div> </div> `; } function renderSecurity() { return ` <div class="profile-card p-6 shadow-sm"> <h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-2"><i class="fas fa-lock text-blue-500"></i> Безопасность аккаунта</h2> <div class="max-w-md space-y-5"> <div><label class="block text-sm font-medium text-gray-700 mb-2">Текущий пароль</label><div class="relative"><input type="password" id="currentPassword" placeholder="Введите текущий пароль" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"><button type="button" class="toggle-password absolute right-3 top-1/2 -translate-y-1/2 text-gray-400"><i class="fas fa-eye"></i></button></div></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Новый пароль</label><div class="relative"><input type="password" id="newPassword" placeholder="Минимум 6 символов" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"><button type="button" class="toggle-password absolute right-3 top-1/2 -translate-y-1/2 text-gray-400"><i class="fas fa-eye"></i></button></div><div id="passwordStrength" class="text-xs mt-1"></div></div> <div><label class="block text-sm font-medium text-gray-700 mb-2">Подтверждение пароля</label><div class="relative"><input type="password" id="confirmPassword" placeholder="Повторите новый пароль" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"><button type="button" class="toggle-password absolute right-3 top-1/2 -translate-y-1/2 text-gray-400"><i class="fas fa-eye"></i></button></div></div> <button id="changePasswordBtn" class="btn-primary text-white px-6 py-2.5 rounded-xl font-medium"><i class="fas fa-key"></i> Сменить пароль</button> </div> <div class="mt-8 pt-6 border-t border-gray-100"><h3 class="font-semibold text-gray-800 flex items-center gap-2 mb-3"><i class="fas fa-globe"></i> Активные сессии</h3><div class="bg-gray-50 rounded-xl p-4"><div class="flex justify-between items-center"><div class="flex items-center gap-3"><i class="fas fa-laptop text-gray-400 text-xl"></i><div><p class="text-sm font-medium">Текущее устройство</p><p class="text-xs text-gray-500">Chrome · Москва</p></div></div><span class="text-xs text-green-500"><i class="fas fa-circle text-[8px]"></i> Активен</span></div></div></div> </div> `; } function renderSupport() { return ` <div class="profile-card p-6 shadow-sm"> <h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-2"><i class="fas fa-headset text-blue-500"></i> Служба поддержки</h2> <div class="grid md:grid-cols-2 gap-6"> <div class="bg-gray-50 rounded-xl p-5"><h3 class="font-semibold text-gray-800 mb-4 flex items-center gap-2"><i class="fas fa-phone-alt text-green-500"></i> Контактные данные</h3><div class="space-y-3"><div class="flex items-center gap-3 text-sm"><i class="fas fa-phone text-gray-400 w-5"></i><span>+7 (495) 123-45-67</span><span class="text-xs text-gray-400">Пн-Пт 09:00-21:00</span></div><div class="flex items-center gap-3 text-sm"><i class="fas fa-envelope text-gray-400 w-5"></i><span>support@logismart.ru</span></div><div class="flex items-center gap-3 text-sm"><i class="fas fa-clock text-gray-400 w-5"></i><span>Среднее время ответа: 15 минут</span></div></div></div> <div class="bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl p-5 text-white"><h3 class="font-semibold mb-4 flex items-center gap-2"><i class="fab fa-telegram"></i> Telegram поддержка</h3><p class="text-sm text-blue-100 mb-4">Быстрая связь с оператором в мессенджере</p><a href="https://t.me/logismart_support" target="_blank" class="inline-flex items-center gap-2 bg-white/20 hover:bg-white/30 px-5 py-2.5 rounded-xl transition"><i class="fab fa-telegram"></i> @logismart_support</a></div> </div> <hr class="my-6"> <div><h3 class="font-semibold text-gray-800 mb-4 flex items-center gap-2"><i class="fas fa-comment-dots text-blue-500"></i> Написать в поддержку</h3><div class="space-y-4"><select id="supportTopic" class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"><option value="">Выберите тему обращения</option><option value="billing">Биллинг и подписка</option><option value="technical">Техническая проблема</option><option value="feature">Предложение по функционалу</option><option value="other">Другое</option></select><textarea id="supportMessage" rows="4" placeholder="Опишите ваш вопрос подробнее..." class="input-field w-full px-4 py-2.5 rounded-xl bg-gray-50"></textarea><button id="sendSupportBtn" class="btn-primary text-white px-6 py-2.5 rounded-xl font-medium"><i class="fas fa-paper-plane"></i> Отправить сообщение</button></div></div> <div class="mt-6 pt-6 border-t border-gray-100"><h3 class="font-semibold text-gray-800 mb-3">Часто задаваемые вопросы</h3><div class="space-y-2 text-sm"><details class="bg-gray-50 rounded-lg p-3"><summary class="cursor-pointer font-medium">Как отключить подписку?</summary><p class="mt-2 text-gray-600 text-xs">Перейдите на вкладку "Подписка" и нажмите "Отключить подписку".</p></details><details class="bg-gray-50 rounded-lg p-3"><summary class="cursor-pointer font-medium">Как добавить новое ТС в автопарк?</summary><p class="mt-2 text-gray-600 text-xs">На вкладке "Автопарк" нажмите кнопку "Добавить ТС".</p></details><details class="bg-gray-50 rounded-lg p-3"><summary class="cursor-pointer font-medium">Не приходит подтверждение на email?</summary><p class="mt-2 text-gray-600 text-xs">Проверьте папку "Спам" или обратитесь в поддержку.</p></details></div></div> </div> `; } // ========== ОБРАБОТЧИКИ ========== function saveProfile() { userData.name = document.getElementById('profileName').value; userData.email = document.getElementById('profileEmail').value; userData.phone = document.getElementById('profilePhone').value; userData.company = document.getElementById('profileCompany').value; userData.inn = document.getElementById('profileInn').value; userData.address = document.getElementById('profileAddress').value; saveUserData(); showToast('Личные данные обновлены', 'success'); } function changeSubscription(planKey) { if (userData.subscription.plan === planKey) return; if (planKey === 'free' && userData.subscription.plan !== 'free') { if (confirm('Отключить платную подписку?')) { userData.subscription.plan = 'free'; saveUserData(); showToast('Подписка отключена', 'warning'); loadTab('subscription'); } } else if (planKey !== 'free') { userData.subscription.plan = planKey; userData.subscription.expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; saveUserData(); showToast(`Тариф "${subscriptionPlans[planKey].name}" активирован!`, 'success'); loadTab('subscription'); } } function toggleAutoRenew() { userData.subscription.autoRenew = !userData.subscription.autoRenew; saveUserData(); showToast(`Автопродление ${userData.subscription.autoRenew ? 'включено' : 'отключено'}`, 'info'); loadTab('subscription'); } function changePassword() { const current = document.getElementById('currentPassword').value; const newPass = document.getElementById('newPassword').value; const confirm = document.getElementById('confirmPassword').value; if (!current) { showToast('Введите текущий пароль', 'error'); return; } if (newPass.length < 6) { showToast('Новый пароль должен быть не менее 6 символов', 'error'); return; } if (newPass !== confirm) { showToast('Пароли не совпадают', 'error'); return; } showToast('Пароль успешно изменён!', 'success'); document.getElementById('currentPassword').value = ''; document.getElementById('newPassword').value = ''; document.getElementById('confirmPassword').value = ''; } function sendSupport() { const topic = document.getElementById('supportTopic').value; const message = document.getElementById('supportMessage').value; if (!topic || !message) { showToast('Заполните тему и сообщение', 'error'); return; } showToast('Сообщение отправлено! Оператор ответит в течение 15 минут', 'success'); document.getElementById('supportTopic').value = ''; document.getElementById('supportMessage').value = ''; } // ========== УПРАВЛЕНИЕ ВКЛАДКАМИ ========== function loadTab(tabId) { currentTab = tabId; let content = ''; if (tabId === 'profile') content = renderProfile(); else if (tabId === 'fleet') content = renderFleet(); else if (tabId === 'subscription') content = renderSubscription(); else if (tabId === 'security') content = renderSecurity(); else if (tabId === 'support') content = renderSupport(); document.getElementById('tabContent').innerHTML = content; if (tabId === 'profile') { document.getElementById('saveProfileBtn')?.addEventListener('click', saveProfile); } if (tabId === 'fleet') { updateFleetStats(); document.getElementById('addVehicleBtn')?.addEventListener('click', openAddModal); document.querySelectorAll('.edit-vehicle-btn').forEach(btn => { btn.addEventListener('click', () => openEditModal(btn.dataset.id)); }); document.querySelectorAll('.delete-vehicle-btn').forEach(btn => { btn.addEventListener('click', () => deleteVehicle(btn.dataset.id)); }); document.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', () => setFilter(btn.dataset.filter)); if (btn.dataset.filter === currentFilter) btn.classList.add('active'); else btn.classList.remove('active'); }); document.getElementById('emptyAddBtn')?.addEventListener('click', openAddModal); } if (tabId === 'subscription') { document.querySelectorAll('.select-plan-btn').forEach(btn => { btn.addEventListener('click', () => changeSubscription(btn.dataset.plan)); }); document.getElementById('toggleAutoRenewBtn')?.addEventListener('click', toggleAutoRenew); document.getElementById('cancelSubscriptionBtn')?.addEventListener('click', () => changeSubscription('free')); } if (tabId === 'security') { document.querySelectorAll('.toggle-password').forEach(btn => { btn.addEventListener('click', (e) => { const input = btn.parentElement.querySelector('input'); input.type = input.type === 'password' ? 'text' : 'password'; btn.innerHTML = input.type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>'; }); }); document.getElementById('changePasswordBtn')?.addEventListener('click', changePassword); document.getElementById('newPassword')?.addEventListener('input', (e) => { const strength = e.target.value.length >= 6 ? '✓ Надёжный пароль' : '✗ Минимум 6 символов'; document.getElementById('passwordStrength').innerHTML = strength; document.getElementById('passwordStrength').className = e.target.value.length >= 6 ? 'text-green-600 text-xs mt-1' : 'text-red-500 text-xs mt-1'; }); } if (tabId === 'support') { document.getElementById('sendSupportBtn')?.addEventListener('click', sendSupport); } document.querySelectorAll('.tab-btn').forEach(btn => { if (btn.dataset.tab === tabId) btn.classList.add('active'); else btn.classList.remove('active'); }); } // ========== ИНИЦИАЛИЗАЦИЯ ========== function init() { loadUserData(); document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => loadTab(btn.dataset.tab)); }); document.getElementById('saveVehicleBtn')?.addEventListener('click', saveVehicle); document.getElementById('closeModalBtn')?.addEventListener('click', closeModal); document.getElementById('cancelModalBtn')?.addEventListener('click', closeModal); document.getElementById('vehicleModal')?.addEventListener('click', (e) => { if (e.target === document.getElementById('vehicleModal')) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && document.getElementById('vehicleModal')?.style.display === 'flex') closeModal(); }); loadTab('profile'); } init(); </script> </body> </html>