feat: add room dashboard with KPI bar and floor grid
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,8 @@ const exchangeRoutes = require('./routes/exchange.routes');
|
||||
const hotelRoutes = require('./routes/hotelpl.routes');
|
||||
const restaurantRoutes = require('./routes/restaurantpl.routes');
|
||||
const incomehrxRoutes = require('./routes/incomehrx.routes');
|
||||
const roomsRoutes = require('./routes/rooms.routes');
|
||||
const dashboardRoutes = require('./routes/dashboard.routes');
|
||||
|
||||
//Prefijo - Auth routes are public (no middleware)
|
||||
app.use('/api/auth', authRoutes);
|
||||
@@ -54,5 +56,7 @@ app.use('/api/purchases', authMiddleware, purchaseRoutes);
|
||||
app.use('/api/exchange', authMiddleware, exchangeRoutes);
|
||||
app.use('/api/hotelpl', authMiddleware, hotelRoutes);
|
||||
app.use('/api/restaurantpl', authMiddleware, restaurantRoutes);
|
||||
app.use('/api/rooms', authMiddleware, roomsRoutes);
|
||||
app.use('/api/dashboard', authMiddleware, dashboardRoutes);
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
const pool = require('../db/connection');
|
||||
|
||||
const getKPIs = async (req, res) => {
|
||||
try {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
const [totalRooms, availableRooms, checkIns, checkOuts] = await Promise.all([
|
||||
pool.query('SELECT COUNT(*) as count FROM rooms'),
|
||||
pool.query("SELECT COUNT(*) as count FROM rooms WHERE status = 'available'"),
|
||||
pool.query("SELECT COUNT(*) as count FROM reservations WHERE check_in = $1 AND status IN ('confirmed', 'checked_in')", [today]),
|
||||
pool.query("SELECT COUNT(*) as count FROM reservations WHERE check_out = $1 AND status = 'checked_in'", [today]),
|
||||
]);
|
||||
|
||||
const total = parseInt(totalRooms.rows[0].count);
|
||||
const available = parseInt(availableRooms.rows[0].count);
|
||||
const occupancy = total > 0 ? Math.round(((total - available) / total) * 100) : 0;
|
||||
|
||||
res.json({
|
||||
occupancy,
|
||||
availableRooms: available,
|
||||
totalRooms: total,
|
||||
todayCheckIns: parseInt(checkIns.rows[0].count),
|
||||
todayCheckOuts: parseInt(checkOuts.rows[0].count),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: 'Error al obtener KPIs' });
|
||||
}
|
||||
};
|
||||
|
||||
const getWeeklyRevenue = async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT DATE(check_out) as day, COALESCE(SUM(total_amount), 0) as revenue
|
||||
FROM reservations
|
||||
WHERE check_out >= CURRENT_DATE - INTERVAL '7 days' AND status = 'checked_out'
|
||||
GROUP BY DATE(check_out) ORDER BY day
|
||||
`);
|
||||
res.json({ weeklyRevenue: result.rows });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: 'Error al obtener ingresos semanales' });
|
||||
}
|
||||
};
|
||||
|
||||
const getTodayArrivals = async (req, res) => {
|
||||
try {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const result = await pool.query(`
|
||||
SELECT r.id, r.check_in, r.check_out, r.room_id, r.status,
|
||||
g.first_name, g.last_name, g.phone
|
||||
FROM reservations r
|
||||
JOIN guests g ON g.id = r.guest_id
|
||||
WHERE (r.check_in = $1 OR r.check_out = $1)
|
||||
AND r.status IN ('confirmed', 'checked_in')
|
||||
ORDER BY r.check_in
|
||||
`, [today]);
|
||||
res.json({ arrivals: result.rows });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: 'Error al obtener llegadas' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getKPIs, getWeeklyRevenue, getTodayArrivals };
|
||||
62
backend/hotel_hacienda/src/controllers/rooms.controller.js
Normal file
62
backend/hotel_hacienda/src/controllers/rooms.controller.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const pool = require('../db/connection');
|
||||
|
||||
const getRoomsWithStatus = async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT r.*,
|
||||
res.id as reservation_id,
|
||||
g.first_name || ' ' || g.last_name as guest_name,
|
||||
g.phone as guest_phone,
|
||||
res.check_in,
|
||||
res.check_out
|
||||
FROM rooms r
|
||||
LEFT JOIN reservations res ON res.room_id = r.id_room AND res.status = 'checked_in'
|
||||
LEFT JOIN guests g ON g.id = res.guest_id
|
||||
ORDER BY r.floor, r.name_room
|
||||
`);
|
||||
res.json({ rooms: result.rows });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: 'Error al obtener estado de habitaciones' });
|
||||
}
|
||||
};
|
||||
|
||||
const updateRoomStatus = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
const userId = req.user?.user_id;
|
||||
|
||||
const validStatuses = ['available', 'occupied', 'cleaning', 'maintenance'];
|
||||
if (!validStatuses.includes(status)) {
|
||||
return res.status(400).json({ message: 'Estado invalido' });
|
||||
}
|
||||
|
||||
const current = await pool.query('SELECT status FROM rooms WHERE id_room = $1', [id]);
|
||||
if (current.rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Habitacion no encontrada' });
|
||||
}
|
||||
const previousStatus = current.rows[0].status;
|
||||
|
||||
await pool.query('UPDATE rooms SET status = $1 WHERE id_room = $2', [status, id]);
|
||||
|
||||
await pool.query(
|
||||
'INSERT INTO room_status_log (room_id, previous_status, new_status, changed_by) VALUES ($1, $2, $3, $4)',
|
||||
[id, previousStatus, status, userId]
|
||||
);
|
||||
|
||||
if (status === 'cleaning') {
|
||||
await pool.query(
|
||||
`INSERT INTO housekeeping_tasks (room_id, priority, type, status) VALUES ($1, 'high', 'checkout', 'pending')`,
|
||||
[id]
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ message: 'Estado actualizado correctamente' });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ message: 'Error al actualizar estado' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getRoomsWithStatus, updateRoomStatus };
|
||||
9
backend/hotel_hacienda/src/routes/dashboard.routes.js
Normal file
9
backend/hotel_hacienda/src/routes/dashboard.routes.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const dashboardController = require('../controllers/dashboard.controller');
|
||||
|
||||
router.get('/kpis', dashboardController.getKPIs);
|
||||
router.get('/weekly-revenue', dashboardController.getWeeklyRevenue);
|
||||
router.get('/today-arrivals', dashboardController.getTodayArrivals);
|
||||
|
||||
module.exports = router;
|
||||
8
backend/hotel_hacienda/src/routes/rooms.routes.js
Normal file
8
backend/hotel_hacienda/src/routes/rooms.routes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const roomsController = require('../controllers/rooms.controller');
|
||||
|
||||
router.get('/status', roomsController.getRoomsWithStatus);
|
||||
router.put('/:id/status', roomsController.updateRoomStatus);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user