Исправление путей к статическим файлам
- Перенос trips-calendar.css в /static/css/ - Перенос upcoming-trips.js и trip-form-loader.js в /static/js/ - Удаление файлов из неправильной папки /public/
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Стили для календаря поездок (карточки из upcoming-trips.json)
|
||||
* Заменяет внешний Tockify виджет
|
||||
*/
|
||||
|
||||
.trips-calendar {
|
||||
margin: 20px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.trips-calendar .trip-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.trips-calendar .trip-image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-card:hover .trip-image img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.trips-calendar .trip-overlay {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-content h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 1.4em;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-details p {
|
||||
margin: 0 0 16px 0;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-meta span {
|
||||
background: #f0f4f8;
|
||||
color: #2d3748;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85em;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
@media (min-width: 768px) {
|
||||
.trips-calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.trips-calendar {
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Состояние загрузки */
|
||||
.trips-calendar .loading {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.trips-calendar .error {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #e53e3e;
|
||||
background: #fed7d7;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.trips-calendar .no-trips {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
background: #f7fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed #cbd5e0;
|
||||
}
|
||||
|
||||
/* Темная тема (если используется) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.trips-calendar .trip-card {
|
||||
background: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-content h3 {
|
||||
color: #f7fafc;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-details p {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.trips-calendar .trip-meta span {
|
||||
background: #4a5568;
|
||||
color: #e2e8f0;
|
||||
border-color: #718096;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Динамическое заполнение dropdown формы поездками из upcoming-trips.json
|
||||
* Синхронизировано с Telegram Bot управлением
|
||||
*/
|
||||
|
||||
class TripFormLoader {
|
||||
constructor(selectId = 'trip_period', jsonPath = '/data/upcoming-trips.json') {
|
||||
this.selectId = selectId;
|
||||
this.jsonPath = jsonPath;
|
||||
}
|
||||
|
||||
async loadTripsData() {
|
||||
try {
|
||||
const response = await fetch(this.jsonPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных поездок для формы:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async populateTripsDropdown() {
|
||||
console.log(`🔍 Ищем dropdown с ID: "${this.selectId}"`);
|
||||
const select = document.getElementById(this.selectId);
|
||||
if (!select) {
|
||||
console.error(`❌ Dropdown с ID "${this.selectId}" не найден`);
|
||||
console.log('📋 Доступные элементы с ID:', Array.from(document.querySelectorAll('[id]')).map(el => el.id));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Dropdown найден, загружаем данные из: ${this.jsonPath}`);
|
||||
const data = await this.loadTripsData();
|
||||
if (!data || !data.trips) {
|
||||
console.error('❌ Не удалось загрузить данные поездок');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📊 Данные загружены: ${data.trips.length} поездок всего`);
|
||||
|
||||
// Фильтруем только активные поездки и сортируем по порядку
|
||||
const activeTrips = data.trips
|
||||
.filter(trip => trip.active === true)
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
|
||||
// Очищаем существующие опции (кроме первой пустой)
|
||||
const firstOption = select.querySelector('option[value=""]');
|
||||
select.innerHTML = '';
|
||||
|
||||
// Возвращаем первую пустую опцию
|
||||
if (firstOption) {
|
||||
select.appendChild(firstOption);
|
||||
} else {
|
||||
const emptyOption = document.createElement('option');
|
||||
emptyOption.value = '';
|
||||
emptyOption.textContent = '';
|
||||
select.appendChild(emptyOption);
|
||||
}
|
||||
|
||||
// Добавляем активные поездки
|
||||
activeTrips.forEach(trip => {
|
||||
const option = document.createElement('option');
|
||||
option.value = trip.title;
|
||||
option.textContent = trip.title;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
// Добавляем статичную опцию "Свой вариант без БВС"
|
||||
const customOption = document.createElement('option');
|
||||
customOption.value = 'Свой вариант без БВС';
|
||||
customOption.textContent = 'Свой вариант без БВС';
|
||||
select.appendChild(customOption);
|
||||
|
||||
console.log(`✅ Dropdown формы заполнен: ${activeTrips.length} активных поездок`);
|
||||
}
|
||||
|
||||
// Метод для обновления (можно вызывать для перезагрузки)
|
||||
async refresh() {
|
||||
await this.populateTripsDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Глобальная переменная для доступа к загрузчику
|
||||
let tripFormLoader;
|
||||
|
||||
// Инициализация при загрузке DOM
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Проверяем, есть ли на странице dropdown для поездок
|
||||
const tripSelect = document.getElementById('trip_period');
|
||||
if (tripSelect) {
|
||||
tripFormLoader = new TripFormLoader();
|
||||
tripFormLoader.populateTripsDropdown();
|
||||
|
||||
console.log('🗓️ Загрузчик поездок для формы инициализирован');
|
||||
}
|
||||
});
|
||||
|
||||
// Функция для обновления dropdown (можно вызывать извне)
|
||||
function refreshTripFormDropdown() {
|
||||
if (tripFormLoader) {
|
||||
tripFormLoader.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Экспорт для использования в других скриптах
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TripFormLoader;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Динамическая загрузка карточек предстоящих поездок
|
||||
* Загружает данные из JSON файла и генерирует HTML карточки
|
||||
*/
|
||||
|
||||
class UpcomingTripsLoader {
|
||||
constructor(containerId = 'trips-grid', jsonPath = '/data/upcoming-trips.json') {
|
||||
this.containerId = containerId;
|
||||
this.jsonPath = jsonPath;
|
||||
this.tripsData = null;
|
||||
}
|
||||
|
||||
async loadTripsData() {
|
||||
try {
|
||||
const response = await fetch(this.jsonPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
this.tripsData = await response.json();
|
||||
return this.tripsData;
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных поездок:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
generateTripCard(trip) {
|
||||
const metaHtml = trip.meta ? trip.meta.map(meta =>
|
||||
`<span>${meta}</span>`
|
||||
).join('') : '';
|
||||
|
||||
return `
|
||||
<div class="trip-card" data-value="${trip.title}">
|
||||
<div class="trip-image">
|
||||
<img src="${trip.image}" alt="${trip.title}" loading="lazy">
|
||||
<div class="trip-overlay">
|
||||
<span class="trip-period">${trip.period}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trip-content">
|
||||
<h3>${trip.title}</h3>
|
||||
<div class="trip-details">
|
||||
<p>${trip.description}</p>
|
||||
<div class="trip-meta">
|
||||
${metaHtml}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Кнопка выбора поездки убрана - dropdown заполняется автоматически -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async renderTrips() {
|
||||
const container = document.getElementById(this.containerId);
|
||||
if (!container) {
|
||||
console.error(`Контейнер с ID "${this.containerId}" не найден`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
container.innerHTML = '<div class="loading">Загрузка поездок...</div>';
|
||||
|
||||
const data = await this.loadTripsData();
|
||||
if (!data || !data.trips) {
|
||||
container.innerHTML = '<div class="error">Ошибка загрузки данных поездок</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Фильтруем только активные поездки и сортируем по порядку
|
||||
const activeTrips = data.trips
|
||||
.filter(trip => trip.active === true)
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
|
||||
if (activeTrips.length === 0) {
|
||||
container.innerHTML = '<div class="no-trips">Нет активных поездок</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Генерируем HTML для всех активных поездок
|
||||
const tripsHtml = activeTrips.map(trip => this.generateTripCard(trip)).join('');
|
||||
container.innerHTML = tripsHtml;
|
||||
|
||||
console.log(`✅ Загружено ${activeTrips.length} активных поездок`);
|
||||
}
|
||||
|
||||
// Метод для обновления данных (можно вызывать для перезагрузки)
|
||||
async refresh() {
|
||||
await this.renderTrips();
|
||||
}
|
||||
|
||||
// Получить данные о конкретной поездке
|
||||
getTripById(id) {
|
||||
if (!this.tripsData || !this.tripsData.trips) return null;
|
||||
return this.tripsData.trips.find(trip => trip.id === id);
|
||||
}
|
||||
|
||||
// Получить все активные поездки
|
||||
getActiveTrips() {
|
||||
if (!this.tripsData || !this.tripsData.trips) return [];
|
||||
return this.tripsData.trips.filter(trip => trip.active === true);
|
||||
}
|
||||
}
|
||||
|
||||
// Глобальная переменная для доступа к загрузчику
|
||||
let upcomingTripsLoader;
|
||||
|
||||
// Инициализация при загрузке DOM
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Проверяем, есть ли на странице контейнер для поездок
|
||||
const tripsContainer = document.getElementById('trips-grid');
|
||||
if (tripsContainer) {
|
||||
upcomingTripsLoader = new UpcomingTripsLoader();
|
||||
upcomingTripsLoader.renderTrips();
|
||||
|
||||
console.log('🗓️ Загрузчик предстоящих поездок инициализирован');
|
||||
}
|
||||
});
|
||||
|
||||
// Функция для обновления поездок (можно вызывать извне)
|
||||
function refreshUpcomingTrips() {
|
||||
if (upcomingTripsLoader) {
|
||||
upcomingTripsLoader.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Экспорт для использования в других скриптах
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = UpcomingTripsLoader;
|
||||
}
|
||||
Reference in New Issue
Block a user