Исправление путей к статическим файлам

- Перенос trips-calendar.css в /static/css/
- Перенос upcoming-trips.js и trip-form-loader.js в /static/js/
- Удаление файлов из неправильной папки /public/
This commit is contained in:
Kirik
2025-09-03 12:54:13 +02:00
parent b005106df0
commit 7eb2f8ae31
3 changed files with 399 additions and 0 deletions
+159
View File
@@ -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;
}
}
+110
View File
@@ -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;
}
+130
View File
@@ -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;
}