✨ Компактные hero секции и улучшенная инициализация БД
🎨 UI улучшения: - Уменьшена высота синих панелей с 100vh до 70vh на главной - Добавлен класс .compact (25vh) для всех остальных страниц - Улучшена адаптивность для мобильных устройств - Обновлены все шаблоны с hero секциями 🚀 Инфраструктура: - Автоматическая инициализация базы данных при деплое - Улучшены мокапные данные (больше отзывов, бронирований, сообщений) - Добавлены настройки сайта в базу данных - Создан скрипт автоматического деплоя deploy.sh 📦 Система сборки: - Обновлен .gitignore с полным покрытием файлов - Добавлена папка для загрузок с .gitkeep - Улучшен README с инструкциями по запуску - ES модули для инициализации базы данных 🐛 Исправления: - Совместимость с ES модулями в Node.js - Правильная обработка ошибок инициализации БД - Корректные SQL запросы для PostgreSQL
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user