- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
303 lines
11 KiB
JavaScript
303 lines
11 KiB
JavaScript
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; |