Компактные hero секции и улучшенная инициализация БД

🎨 UI улучшения:
- Уменьшена высота синих панелей с 100vh до 70vh на главной
- Добавлен класс .compact (25vh) для всех остальных страниц
- Улучшена адаптивность для мобильных устройств
- Обновлены все шаблоны с hero секциями

🚀 Инфраструктура:
- Автоматическая инициализация базы данных при деплое
- Улучшены мокапные данные (больше отзывов, бронирований, сообщений)
- Добавлены настройки сайта в базу данных
- Создан скрипт автоматического деплоя deploy.sh

📦 Система сборки:
- Обновлен .gitignore с полным покрытием файлов
- Добавлена папка для загрузок с .gitkeep
- Улучшен README с инструкциями по запуску
- ES модули для инициализации базы данных

🐛 Исправления:
- Совместимость с ES модулями в Node.js
- Правильная обработка ошибок инициализации БД
- Корректные SQL запросы для PostgreSQL
This commit is contained in:
2025-11-29 18:47:42 +09:00
parent 409e6c146b
commit a461fea9d9
24 changed files with 1442 additions and 84 deletions

View File

@@ -93,6 +93,8 @@ const Bookings = sequelize.define('bookings', {
status: { type: DataTypes.ENUM('pending', 'confirmed', 'cancelled', 'completed'), defaultValue: 'pending' },
total_price: { type: DataTypes.DECIMAL(10, 2), allowNull: false },
notes: { type: DataTypes.TEXT },
guide_id: { type: DataTypes.INTEGER },
route_id: { type: DataTypes.INTEGER },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
@@ -141,6 +143,75 @@ const Admins = sequelize.define('admins', {
tableName: 'admins'
});
// Новые модели для системы рейтинга и расписания
const Ratings = sequelize.define('ratings', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
user_ip: { type: DataTypes.STRING(45), allowNull: false },
target_id: { type: DataTypes.INTEGER, allowNull: false },
target_type: { type: DataTypes.ENUM('route', 'guide', 'article'), allowNull: false },
rating: { type: DataTypes.INTEGER, allowNull: false, validate: { isIn: [[1, -1]] } },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'ratings'
});
const GuideSchedules = sequelize.define('guide_schedules', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
guide_id: { type: DataTypes.INTEGER, allowNull: false },
monday: { type: DataTypes.BOOLEAN, defaultValue: true },
tuesday: { type: DataTypes.BOOLEAN, defaultValue: true },
wednesday: { type: DataTypes.BOOLEAN, defaultValue: true },
thursday: { type: DataTypes.BOOLEAN, defaultValue: true },
friday: { type: DataTypes.BOOLEAN, defaultValue: true },
saturday: { type: DataTypes.BOOLEAN, defaultValue: false },
sunday: { type: DataTypes.BOOLEAN, defaultValue: false },
start_time: { type: DataTypes.TIME, defaultValue: '09:00' },
end_time: { type: DataTypes.TIME, defaultValue: '18:00' },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'guide_schedules'
});
const Holidays = sequelize.define('holidays', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
date: { type: DataTypes.DATEONLY, allowNull: false },
title: { type: DataTypes.STRING, allowNull: false },
type: { type: DataTypes.ENUM('public', 'guide_personal'), allowNull: false },
guide_id: { type: DataTypes.INTEGER },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'holidays'
});
// Определение связей между моделями
Guides.hasOne(GuideSchedules, { foreignKey: 'guide_id' });
GuideSchedules.belongsTo(Guides, { foreignKey: 'guide_id' });
Guides.hasMany(Holidays, { foreignKey: 'guide_id' });
Holidays.belongsTo(Guides, { foreignKey: 'guide_id' });
Guides.hasMany(Bookings, { foreignKey: 'guide_id' });
Bookings.belongsTo(Guides, { foreignKey: 'guide_id' });
Routes.hasMany(Bookings, { foreignKey: 'route_id' });
Bookings.belongsTo(Routes, { foreignKey: 'route_id' });
// Методы для получения рейтингов
const getRatingStats = async (targetType, targetId) => {
const result = await sequelize.query(
'SELECT * FROM calculate_rating(:targetType, :targetId)',
{
replacements: { targetType, targetId },
type: sequelize.QueryTypes.SELECT
}
);
return result[0] || { likes_count: 0, dislikes_count: 0, total_votes: 0, rating_percentage: 0 };
};
// Конфигурация AdminJS с ресурсами базы данных
// Конфигурация AdminJS с ресурсами Sequelize
@@ -457,6 +528,111 @@ const adminJsOptions = {
}
},
}
},
{
resource: Ratings,
options: {
parent: { name: 'Система рейтингов', icon: 'Star' },
listProperties: ['id', 'target_type', 'target_id', 'rating', 'user_ip', 'created_at'],
showProperties: ['id', 'target_type', 'target_id', 'rating', 'user_ip', 'created_at'],
filterProperties: ['target_type', 'target_id', 'rating'],
properties: {
target_type: {
availableValues: [
{ value: 'route', label: 'Маршрут' },
{ value: 'guide', label: 'Гид' },
{ value: 'article', label: 'Статья' }
],
},
rating: {
availableValues: [
{ value: 1, label: '👍 Лайк' },
{ value: -1, label: '👎 Дизлайк' }
],
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
actions: {
new: { isAccessible: false },
edit: { isAccessible: false },
delete: { isAccessible: true },
list: { isAccessible: true },
show: { isAccessible: true }
}
}
},
{
resource: GuideSchedules,
options: {
parent: { name: 'Управление гидами', icon: 'Calendar' },
listProperties: ['id', 'guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time'],
editProperties: ['guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time'],
showProperties: ['id', 'guide_id', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'start_time', 'end_time', 'created_at', 'updated_at'],
filterProperties: ['guide_id'],
properties: {
guide_id: {
isTitle: true,
isRequired: true,
},
monday: { type: 'boolean' },
tuesday: { type: 'boolean' },
wednesday: { type: 'boolean' },
thursday: { type: 'boolean' },
friday: { type: 'boolean' },
saturday: { type: 'boolean' },
sunday: { type: 'boolean' },
start_time: {
type: 'string',
description: 'Время начала работы (формат HH:MM)'
},
end_time: {
type: 'string',
description: 'Время окончания работы (формат HH:MM)'
},
created_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
},
updated_at: {
isVisible: { list: false, filter: false, show: true, edit: false },
}
},
}
},
{
resource: Holidays,
options: {
parent: { name: 'Управление гидами', icon: 'Calendar' },
listProperties: ['id', 'date', 'title', 'type', 'guide_id', 'created_at'],
editProperties: ['date', 'title', 'type', 'guide_id'],
showProperties: ['id', 'date', 'title', 'type', 'guide_id', 'created_at'],
filterProperties: ['date', 'type', 'guide_id'],
properties: {
date: {
isTitle: true,
isRequired: true,
type: 'date'
},
title: {
isRequired: true,
description: 'Название выходного дня'
},
type: {
availableValues: [
{ value: 'public', label: 'Общий выходной' },
{ value: 'guide_personal', label: 'Личный выходной гида' }
],
isRequired: true
},
guide_id: {
description: 'Оставить пустым для общих выходных'
},
created_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
}
],
rootPath: '/admin',