424 lines
17 KiB
JavaScript
424 lines
17 KiB
JavaScript
// AI Trip Planner - Логика приложения
|
||
|
||
// Моковые данные для генерации маршрутов
|
||
const destinations = {
|
||
beach: {
|
||
name: 'Пляжный отдых',
|
||
locations: ['Пляж Золотой', 'Прибрежный бульвар', 'Морской порт', 'Аквапарк', 'Дайвинг-центр'],
|
||
activities: {
|
||
food: ['Рыбный ресторан', 'Кафе у моря', 'Ужин на закате'],
|
||
photo: ['Фотосессия на пляже', 'Маяк для фото', 'Закатная точка'],
|
||
history: ['Морской музей', 'Старый порт'],
|
||
art: ['Галерея маринистики'],
|
||
nightlife: ['Пляжный бар', 'Ночной клуб у моря'],
|
||
shopping: ['Рынок сувениров', 'ТЦ прибрежный'],
|
||
sports: ['Водные лыжи', 'Пляжный волейбол', 'Сёрфинг'],
|
||
relax: ['SPA-центр', 'Массаж на пляже'],
|
||
adventure: ['Прогулка на катере', 'Снорклинг'],
|
||
local: ['Рыбацкая деревня', 'Местный рынок']
|
||
}
|
||
},
|
||
active: {
|
||
name: 'Активный отдых',
|
||
locations: ['Горнолыжный курорт', 'Скалодром', 'Велотропа', 'Парк приключений', 'Река для рафтинга'],
|
||
activities: {
|
||
food: ['Горное кафе', 'Ресторан местной кухни', 'Эко-кафе'],
|
||
photo: ['Панорамная площадка', 'Водопад для фото'],
|
||
history: ['Краеведческий музей'],
|
||
art: ['Галерея современного искусства'],
|
||
nightlife: ['Бар после спорта', 'Лаунж-зона'],
|
||
shopping: ['Спортивный магазин', 'Сувениры'],
|
||
sports: ['Скалолазание', 'Рафтинг', 'Велопрогулка', 'Зиплайн', 'Лыжи'],
|
||
relax: ['Баня после спорта', 'Массаж'],
|
||
adventure: ['Прыжок с тарзанки', 'Каньонинг', 'Полёт на параплане'],
|
||
local: ['Горное село', 'Местные традиции']
|
||
}
|
||
},
|
||
culture: {
|
||
name: 'Культурный отдых',
|
||
locations: ['Исторический центр', 'Музей искусств', 'Театр оперы', 'Старый город', 'Храм'],
|
||
activities: {
|
||
food: ['Ресторан высокой кухни', 'Кафе в старом городе', 'Гастро-тур'],
|
||
photo: ['Архитектурные шедевры', 'Исторические улочки'],
|
||
history: ['Краеведческий музей', 'Экскурсия по храмам', 'Древние руины'],
|
||
art: ['Художественный музей', 'Арт-галерея', 'Мастер-класс по искусству'],
|
||
nightlife: ['Театральная премьера', 'Джаз-клуб'],
|
||
shopping: ['Антикварный магазин', 'Книжный', 'Галерея сувениров'],
|
||
sports: ['Прогулка по городу'],
|
||
relax: ['Чайная церемония', 'Медитация в саду'],
|
||
adventure: ['Квест по городу', 'Ночная экскурсия'],
|
||
local: ['Встреча с местными', 'Традиционный ужин']
|
||
}
|
||
},
|
||
nature: {
|
||
name: 'Отдых на природе',
|
||
locations: ['Национальный парк', 'Озеро', 'Лесной массив', 'Горная река', 'Заповедник'],
|
||
activities: {
|
||
food: ['Пикник на природе', 'Кафе у озера', 'Эко-ресторан'],
|
||
photo: ['Животные в заповеднике', 'Пейзажи', 'Рассвет в горах'],
|
||
history: ['Музей природы', 'Эко-тропа с информацией'],
|
||
art: ['Пленэр для рисования'],
|
||
nightlife: ['Наблюдение за звёздами', 'Костёр под звёздами'],
|
||
shopping: ['Эко-лавка', 'Местные продукты'],
|
||
sports: ['Хайкинг', 'Каякинг', 'Наблюдение за птицами', 'Рыбалка'],
|
||
relax: ['Йога на природе', 'Медитация у озера'],
|
||
adventure: ['Поход с палатками', 'Сплавление по реке'],
|
||
local: ['Встреча с егерем', 'Местные легенды']
|
||
}
|
||
}
|
||
};
|
||
|
||
const weatherTips = {
|
||
beach: '☀️ Лучшее время: май-сентябрь. Не забудь солнцезащитный крем и головной убор.',
|
||
active: '🏔 Погода переменчива в горах. Возьми ветровку и удобную обувь.',
|
||
culture: '🏛 Идеально в любое время года. Многие музеи работают до 20:00.',
|
||
nature: '🌲 Бери репелленты и удобную обувь. Весной и осенью особенно красиво.'
|
||
};
|
||
|
||
const budgetTips = {
|
||
low: '💡 Бюджет ограничен? Сосредоточься на бесплатных активностях: прогулки, пляжи, парки.',
|
||
medium: '💡 Средний бюджет позволяет добавить платные экскурсии и хорошие рестораны.',
|
||
high: '💡 С таким бюджетом можно позволить VIP-услуги и эксклюзивные впечатления.'
|
||
};
|
||
|
||
let currentTrip = null;
|
||
let selectedInterests = [];
|
||
|
||
// Инициализация
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
initInterestTags();
|
||
loadSavedTrips();
|
||
});
|
||
|
||
function initInterestTags() {
|
||
const tags = document.querySelectorAll('.interest-tag');
|
||
tags.forEach(tag => {
|
||
tag.addEventListener('click', () => {
|
||
tag.classList.toggle('selected');
|
||
const tagName = tag.dataset.tag;
|
||
if (selectedInterests.includes(tagName)) {
|
||
selectedInterests = selectedInterests.filter(i => i !== tagName);
|
||
} else {
|
||
selectedInterests.push(tagName);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function scrollToSection(id) {
|
||
document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
function generateTrip() {
|
||
const budget = parseInt(document.getElementById('budget').value);
|
||
const days = parseInt(document.getElementById('days').value);
|
||
const restType = document.getElementById('restType').value;
|
||
|
||
if (!budget || !days) {
|
||
alert('Пожалуйста, заполните бюджет и количество дней');
|
||
return;
|
||
}
|
||
|
||
if (selectedInterests.length === 0) {
|
||
selectedInterests = ['food', 'photo', 'relax'];
|
||
}
|
||
|
||
currentTrip = createRoute(budget, days, restType, selectedInterests);
|
||
displayRoute(currentTrip);
|
||
document.getElementById('results').classList.remove('hidden');
|
||
scrollToSection('results');
|
||
}
|
||
|
||
function createRoute(budget, days, restType, interests) {
|
||
const dest = destinations[restType];
|
||
const trip = {
|
||
id: Date.now(),
|
||
date: new Date().toISOString(),
|
||
budget: budget,
|
||
days: days,
|
||
restType: restType,
|
||
interests: interests,
|
||
totalSpent: 0,
|
||
routeDays: []
|
||
};
|
||
|
||
const budgetPerDay = Math.floor(budget / days);
|
||
const locationBudget = Math.floor(budgetPerDay * 0.4);
|
||
const foodBudget = Math.floor(budgetPerDay * 0.3);
|
||
const activityBudget = Math.floor(budgetPerDay * 0.3);
|
||
|
||
for (let day = 1; day <= days; day++) {
|
||
const dayPlan = {
|
||
day: day,
|
||
title: `День ${day}: ${dest.locations[(day - 1) % dest.locations.length]}`,
|
||
activities: []
|
||
};
|
||
|
||
const morningTime = 9 + Math.floor(Math.random() * 2);
|
||
const afternoonTime = morningTime + 3;
|
||
const eveningTime = afternoonTime + 4;
|
||
|
||
const interest1 = interests[day % interests.length];
|
||
const interest2 = interests[(day + 1) % interests.length];
|
||
const interest3 = interests[(day + 2) % interests.length];
|
||
|
||
const morningActivity = dest.activities[interest1]?.[0] || 'Прогулка';
|
||
const afternoonActivity = dest.activities[interest2]?.[0] || 'Обед';
|
||
const eveningActivity = dest.activities[interest3]?.[0] || 'Отдых';
|
||
|
||
const morningCost = Math.floor(locationBudget * (0.8 + Math.random() * 0.4));
|
||
const afternoonCost = Math.floor(foodBudget * (0.8 + Math.random() * 0.4));
|
||
const eveningCost = Math.floor(activityBudget * (0.8 + Math.random() * 0.4));
|
||
|
||
dayPlan.activities.push({
|
||
time: `${morningTime}:00`,
|
||
name: morningActivity,
|
||
cost: morningCost
|
||
});
|
||
|
||
dayPlan.activities.push({
|
||
time: `${afternoonTime}:00`,
|
||
name: afternoonActivity,
|
||
cost: afternoonCost
|
||
});
|
||
|
||
dayPlan.activities.push({
|
||
time: `${eveningTime}:00`,
|
||
name: eveningActivity,
|
||
cost: eveningCost
|
||
});
|
||
|
||
trip.routeDays.push(dayPlan);
|
||
trip.totalSpent += morningCost + afternoonCost + eveningCost;
|
||
}
|
||
|
||
trip.totalSpent = Math.min(trip.totalSpent, budget);
|
||
|
||
return trip;
|
||
}
|
||
|
||
function displayRoute(trip) {
|
||
const dest = destinations[trip.restType];
|
||
|
||
document.getElementById('spentAmount').textContent = trip.totalSpent.toLocaleString();
|
||
const remaining = trip.budget - trip.totalSpent;
|
||
document.getElementById('remainingAmount').textContent = remaining.toLocaleString();
|
||
|
||
const percent = Math.round((trip.totalSpent / trip.budget) * 100);
|
||
document.getElementById('budgetFill').style.width = percent + '%';
|
||
document.getElementById('budgetPercent').textContent = percent + '% использовано';
|
||
|
||
let tips = '<p><strong>🌤 По сезону:</strong> ' + weatherTips[trip.restType] + '</p>';
|
||
tips += '<p><strong>💰 По бюджету:</strong> ' + getBudgetTip(trip.budget) + '</p>';
|
||
|
||
if (trip.interests.length > 0) {
|
||
const interestNames = trip.interests.map(i => getInterestName(i)).join(', ');
|
||
tips += '<p><strong>🎯 По интересам:</strong> Маршрут оптимизирован под: ' + interestNames + '</p>';
|
||
}
|
||
|
||
document.getElementById('aiTipsContent').innerHTML = tips;
|
||
|
||
let daysHtml = '<div class="trip-card">';
|
||
daysHtml += '<h3 style="margin-bottom:var(--kt-ai-space-4)">📅 Программа по дням</h3>';
|
||
|
||
trip.routeDays.forEach(day => {
|
||
daysHtml += '<div class="trip-day">';
|
||
daysHtml += '<h4>' + day.title + '</h4>';
|
||
|
||
day.activities.forEach(activity => {
|
||
daysHtml += '<div class="activity-row">';
|
||
daysHtml += '<span class="activity-time">' + activity.time + '</span>';
|
||
daysHtml += '<span class="activity-name">' + activity.name + '</span>';
|
||
daysHtml += '<span class="activity-cost">' + activity.cost.toLocaleString() + ' ₸</span>';
|
||
daysHtml += '</div>';
|
||
});
|
||
|
||
daysHtml += '</div>';
|
||
});
|
||
|
||
daysHtml += '</div>';
|
||
document.getElementById('routeDays').innerHTML = daysHtml;
|
||
|
||
let mapPointsHtml = '';
|
||
trip.routeDays.slice(0, 5).forEach((day, index) => {
|
||
const location = day.title.split(': ')[1] || day.title;
|
||
mapPointsHtml += '<span class="map-point">';
|
||
mapPointsHtml += '<svg class="kt-icon" aria-hidden="true"><use href="#pin"></use></svg>';
|
||
mapPointsHtml += '<span>День ' + day.day + ': ' + location + '</span>';
|
||
mapPointsHtml += '</span>';
|
||
});
|
||
document.getElementById('mapPoints').innerHTML = mapPointsHtml;
|
||
}
|
||
|
||
function getInterestName(tag) {
|
||
const names = {
|
||
food: 'Еда', photo: 'Фото', history: 'История', art: 'Искусство',
|
||
nightlife: 'Ночная жизнь', shopping: 'Шопинг', sports: 'Спорт',
|
||
relax: 'Релакс', adventure: 'Приключения', local: 'Местный колорит'
|
||
};
|
||
return names[tag] || tag;
|
||
}
|
||
|
||
function getBudgetTip(budget) {
|
||
if (budget < 200000) return budgetTips.low;
|
||
if (budget < 500000) return budgetTips.medium;
|
||
return budgetTips.high;
|
||
}
|
||
|
||
function saveTrip() {
|
||
if (!currentTrip) return;
|
||
|
||
const saved = JSON.parse(localStorage.getItem('savedTrips') || '[]');
|
||
saved.push(currentTrip);
|
||
localStorage.setItem('savedTrips', JSON.stringify(saved));
|
||
|
||
alert('Маршрут сохранён!');
|
||
loadSavedTrips();
|
||
}
|
||
|
||
function loadSavedTrips() {
|
||
const saved = JSON.parse(localStorage.getItem('savedTrips') || '[]');
|
||
const container = document.getElementById('savedTripsList');
|
||
|
||
if (saved.length === 0) {
|
||
container.innerHTML = '<p style="color:var(--kt-ai-fg-muted);text-align:center;padding:var(--kt-ai-space-6)">Пока нет сохранённых поездок</p>';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
saved.forEach(trip => {
|
||
const date = new Date(trip.date).toLocaleDateString('ru-RU');
|
||
const destName = destinations[trip.restType]?.name || trip.restType;
|
||
|
||
html += '<div class="trip-card">';
|
||
html += '<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:var(--kt-ai-space-2)">';
|
||
html += '<div>';
|
||
html += '<h3 style="margin:0">' + destName + '</h3>';
|
||
html += '<p style="color:var(--kt-ai-fg-muted);margin:var(--kt-ai-space-1) 0 0">' + trip.days + ' дней • ' + trip.budget.toLocaleString() + ' ₸ • ' + date + '</p>';
|
||
html += '</div>';
|
||
html += '<div class="action-buttons">';
|
||
html += '<button class="kt-ai-btn" data-variant="primary" onclick="loadTrip(' + trip.id + ')">';
|
||
html += '<svg class="kt-icon" style="margin-right:var(--kt-ai-space-1)"><use href="#eye"></use></svg>Открыть';
|
||
html += '</button>';
|
||
html += '<button class="kt-ai-btn" data-variant="ghost" onclick="deleteTrip(' + trip.id + ')">';
|
||
html += '<svg class="kt-icon" style="margin-right:var(--kt-ai-space-1)"><use href="#trash"></use></svg>Удалить';
|
||
html += '</button>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function showSavedTrips() {
|
||
scrollToSection('saved');
|
||
}
|
||
|
||
function loadTrip(id) {
|
||
const saved = JSON.parse(localStorage.getItem('savedTrips') || '[]');
|
||
const trip = saved.find(t => t.id === id);
|
||
|
||
if (trip) {
|
||
currentTrip = trip;
|
||
displayRoute(trip);
|
||
document.getElementById('results').classList.remove('hidden');
|
||
scrollToSection('results');
|
||
}
|
||
}
|
||
|
||
function deleteTrip(id) {
|
||
if (!confirm('Удалить этот маршрут?')) return;
|
||
|
||
let saved = JSON.parse(localStorage.getItem('savedTrips') || '[]');
|
||
saved = saved.filter(t => t.id !== id);
|
||
localStorage.setItem('savedTrips', JSON.stringify(saved));
|
||
loadSavedTrips();
|
||
}
|
||
|
||
function clearForm() {
|
||
document.getElementById('budget').value = '';
|
||
document.getElementById('days').value = '';
|
||
document.getElementById('restType').value = 'beach';
|
||
selectedInterests = [];
|
||
document.querySelectorAll('.interest-tag').forEach(tag => tag.classList.remove('selected'));
|
||
document.getElementById('results').classList.add('hidden');
|
||
currentTrip = null;
|
||
}
|
||
|
||
function exportToPDF() {
|
||
if (!currentTrip) return;
|
||
|
||
const printWindow = window.open('', '_blank');
|
||
const content = generateExportContent('text');
|
||
|
||
printWindow.document.write(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>AI Trip Planner - Маршрут</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||
h1 { color: #6366f1; }
|
||
.trip-info { background: #f3f4f6; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||
.day { margin-bottom: 20px; padding-left: 15px; border-left: 3px solid #6366f1; }
|
||
.activity { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #e5e7eb; }
|
||
.budget-bar { height: 10px; background: #e5e7eb; border-radius: 5px; margin: 10px 0; }
|
||
.budget-fill { height: 100%; background: linear-gradient(90deg, #6366f1, #a855f7); border-radius: 5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${content}
|
||
</body>
|
||
</html>
|
||
`);
|
||
|
||
printWindow.document.close();
|
||
printWindow.print();
|
||
}
|
||
|
||
function exportToText() {
|
||
if (!currentTrip) return;
|
||
|
||
const content = generateExportContent('text');
|
||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'ai-trip-route-' + new Date().toISOString().slice(0, 10) + '.txt';
|
||
a.click();
|
||
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function generateExportContent(format) {
|
||
const dest = destinations[currentTrip.restType];
|
||
const date = new Date(currentTrip.date).toLocaleDateString('ru-RU');
|
||
|
||
let content = 'AI TRIP PLANNER\n';
|
||
content += '='.repeat(50) + '\n\n';
|
||
content += 'Тип отдыха: ' + dest.name + '\n';
|
||
content += 'Длительность: ' + currentTrip.days + ' дней(я)\n';
|
||
content += 'Бюджет: ' + currentTrip.budget.toLocaleString() + ' ₸\n';
|
||
content += 'Дата создания: ' + date + '\n';
|
||
content += 'Интересы: ' + currentTrip.interests.map(i => getInterestName(i)).join(', ') + '\n\n';
|
||
|
||
content += 'БЮДЖЕТ\n';
|
||
content += '-'.repeat(30) + '\n';
|
||
content += 'Потрачено: ' + currentTrip.totalSpent.toLocaleString() + ' ₸\n';
|
||
content += 'Осталось: ' + (currentTrip.budget - currentTrip.totalSpent).toLocaleString() + ' ₸\n\n';
|
||
|
||
content += 'МАРШРУТ\n';
|
||
content += '-'.repeat(30) + '\n';
|
||
|
||
currentTrip.routeDays.forEach(day => {
|
||
content += '\n' + day.title + '\n';
|
||
day.activities.forEach(activity => {
|
||
content += ' ' + activity.time + ' - ' + activity.name + ' (' + activity.cost.toLocaleString() + ' ₸)\n';
|
||
});
|
||
});
|
||
|
||
return content;
|
||
}
|