feat: Оптимизация навигации AdminJS в логические группы
- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
This commit is contained in:
303
src/routes/guide-schedules.js
Normal file
303
src/routes/guide-schedules.js
Normal file
@@ -0,0 +1,303 @@
|
||||
import express from 'express';
|
||||
import db from '../config/database.js';
|
||||
const router = express.Router();
|
||||
|
||||
// Получение расписания гидов за месяц
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { year, month, guide_id } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT gwd.*, g.name as guide_name, g.specialization
|
||||
FROM guide_working_days gwd
|
||||
LEFT JOIN guides g ON gwd.guide_id = g.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (year && month) {
|
||||
query += ` AND EXTRACT(YEAR FROM gwd.work_date) = $${params.length + 1} AND EXTRACT(MONTH FROM gwd.work_date) = $${params.length + 2}`;
|
||||
params.push(year, month);
|
||||
}
|
||||
|
||||
if (guide_id) {
|
||||
query += ` AND gwd.guide_id = $${params.length + 1}`;
|
||||
params.push(guide_id);
|
||||
}
|
||||
|
||||
query += ` ORDER BY gwd.work_date, g.name`;
|
||||
|
||||
const result = await db.query(query, params);
|
||||
res.json({ success: true, data: result.rows });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения расписания:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка получения расписания' });
|
||||
}
|
||||
});
|
||||
|
||||
// Массовое создание/обновление расписания гидов за месяц
|
||||
router.put('/', async (req, res) => {
|
||||
try {
|
||||
const { year, month } = req.query;
|
||||
const { schedules } = req.body;
|
||||
|
||||
if (!year || !month || !schedules || !Array.isArray(schedules)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Требуются параметры year, month и массив schedules'
|
||||
});
|
||||
}
|
||||
|
||||
await db.query('BEGIN');
|
||||
|
||||
// Удаляем существующее расписание за этот месяц
|
||||
await db.query(`
|
||||
DELETE FROM guide_working_days
|
||||
WHERE EXTRACT(YEAR FROM work_date) = $1
|
||||
AND EXTRACT(MONTH FROM work_date) = $2
|
||||
`, [year, month]);
|
||||
|
||||
// Добавляем новое расписание
|
||||
if (schedules.length > 0) {
|
||||
const values = schedules.map((schedule, index) => {
|
||||
const baseIndex = index * 2;
|
||||
return `($${baseIndex + 1}, $${baseIndex + 2})`;
|
||||
}).join(', ');
|
||||
|
||||
const params = schedules.flatMap(schedule => [
|
||||
schedule.guide_id,
|
||||
schedule.work_date
|
||||
]);
|
||||
|
||||
await db.query(`
|
||||
INSERT INTO guide_schedules (guide_id, work_date)
|
||||
VALUES ${values}
|
||||
`, params);
|
||||
}
|
||||
|
||||
await db.query('COMMIT');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Расписание за ${year}-${month.toString().padStart(2, '0')} обновлено`,
|
||||
count: schedules.length
|
||||
});
|
||||
} catch (error) {
|
||||
await db.query('ROLLBACK');
|
||||
console.error('Ошибка обновления расписания:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка обновления расписания' });
|
||||
}
|
||||
});
|
||||
|
||||
// Массовое добавление расписания (для копирования)
|
||||
router.post('/batch', async (req, res) => {
|
||||
try {
|
||||
const { schedules } = req.body;
|
||||
|
||||
if (!schedules || !Array.isArray(schedules)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Требуется массив schedules'
|
||||
});
|
||||
}
|
||||
|
||||
if (schedules.length === 0) {
|
||||
return res.json({ success: true, message: 'Нет расписания для добавления' });
|
||||
}
|
||||
|
||||
await db.query('BEGIN');
|
||||
|
||||
// Проверяем существующие записи и добавляем только новые
|
||||
const existingQuery = `
|
||||
SELECT guide_id, work_date
|
||||
FROM guide_working_days
|
||||
WHERE (guide_id, work_date) IN (${schedules.map((_, index) => {
|
||||
const baseIndex = index * 2;
|
||||
return `($${baseIndex + 1}, $${baseIndex + 2})`;
|
||||
}).join(', ')})
|
||||
`;
|
||||
|
||||
const existingParams = schedules.flatMap(schedule => [
|
||||
schedule.guide_id,
|
||||
schedule.work_date
|
||||
]);
|
||||
|
||||
const existingResult = await db.query(existingQuery, existingParams);
|
||||
const existingSet = new Set(
|
||||
existingResult.rows.map(row => `${row.guide_id}-${row.work_date}`)
|
||||
);
|
||||
|
||||
// Фильтруем новые записи
|
||||
const newSchedules = schedules.filter(schedule => {
|
||||
const key = `${schedule.guide_id}-${schedule.work_date}`;
|
||||
return !existingSet.has(key);
|
||||
});
|
||||
|
||||
if (newSchedules.length > 0) {
|
||||
const values = newSchedules.map((schedule, index) => {
|
||||
const baseIndex = index * 2;
|
||||
return `($${baseIndex + 1}, $${baseIndex + 2})`;
|
||||
}).join(', ');
|
||||
|
||||
const params = newSchedules.flatMap(schedule => [
|
||||
schedule.guide_id,
|
||||
schedule.work_date
|
||||
]);
|
||||
|
||||
await db.query(`
|
||||
INSERT INTO guide_schedules (guide_id, work_date)
|
||||
VALUES ${values}
|
||||
`, params);
|
||||
}
|
||||
|
||||
await db.query('COMMIT');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Расписание добавлено',
|
||||
added: newSchedules.length,
|
||||
skipped: schedules.length - newSchedules.length
|
||||
});
|
||||
} catch (error) {
|
||||
await db.query('ROLLBACK');
|
||||
console.error('Ошибка массового добавления расписания:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка добавления расписания' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавление одного рабочего дня
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { guide_id, work_date } = req.body;
|
||||
|
||||
if (!guide_id || !work_date) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Требуются параметры guide_id и work_date'
|
||||
});
|
||||
}
|
||||
|
||||
// Проверяем, что запись не существует
|
||||
const existingResult = await db.query(
|
||||
'SELECT id FROM guide_working_days WHERE guide_id = $1 AND work_date = $2',
|
||||
[guide_id, work_date]
|
||||
);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'Рабочий день уже существует'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await db.query(`
|
||||
INSERT INTO guide_working_days (guide_id, work_date)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *
|
||||
`, [guide_id, work_date]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows[0],
|
||||
message: 'Рабочий день добавлен'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка добавления рабочего дня:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка добавления рабочего дня' });
|
||||
}
|
||||
});
|
||||
|
||||
// Удаление рабочего дня
|
||||
router.delete('/', async (req, res) => {
|
||||
try {
|
||||
const { guide_id, work_date } = req.body;
|
||||
|
||||
if (!guide_id || !work_date) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Требуются параметры guide_id и work_date'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await db.query(`
|
||||
DELETE FROM guide_working_days
|
||||
WHERE guide_id = $1 AND work_date = $2
|
||||
RETURNING *
|
||||
`, [guide_id, work_date]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Рабочий день не найден'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Рабочий день удален'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления рабочего дня:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка удаления рабочего дня' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получение статистики работы гидов
|
||||
router.get('/stats', async (req, res) => {
|
||||
try {
|
||||
const { year, month } = req.query;
|
||||
|
||||
let dateFilter = '';
|
||||
const params = [];
|
||||
|
||||
if (year && month) {
|
||||
dateFilter = 'WHERE EXTRACT(YEAR FROM gwd.work_date) = $1 AND EXTRACT(MONTH FROM gwd.work_date) = $2';
|
||||
params.push(year, month);
|
||||
}
|
||||
|
||||
const statsQuery = `
|
||||
SELECT
|
||||
g.id,
|
||||
g.name,
|
||||
g.specialization,
|
||||
COUNT(gwd.work_date) as working_days,
|
||||
MIN(gwd.work_date) as first_work_date,
|
||||
MAX(gwd.work_date) as last_work_date
|
||||
FROM guides g
|
||||
LEFT JOIN guide_working_days gwd ON g.id = gwd.guide_id
|
||||
${dateFilter}
|
||||
GROUP BY g.id, g.name, g.specialization
|
||||
ORDER BY working_days DESC, g.name
|
||||
`;
|
||||
|
||||
const result = await db.query(statsQuery, params);
|
||||
|
||||
// Общая статистика
|
||||
const totalStats = await db.query(`
|
||||
SELECT
|
||||
COUNT(DISTINCT gwd.guide_id) as active_guides,
|
||||
COUNT(gwd.work_date) as total_working_days,
|
||||
ROUND(AVG(guide_days.working_days), 1) as avg_days_per_guide
|
||||
FROM (
|
||||
SELECT guide_id, COUNT(work_date) as working_days
|
||||
FROM guide_working_days gwd
|
||||
${dateFilter}
|
||||
GROUP BY guide_id
|
||||
) guide_days
|
||||
RIGHT JOIN guides g ON guide_days.guide_id = g.id
|
||||
`, params);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
guides: result.rows,
|
||||
summary: totalStats.rows[0]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения статистики:', error);
|
||||
res.status(500).json({ success: false, error: 'Ошибка получения статистики' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user