// rooms-page.jsx — Room management page (card grid)
// Shows live status, current occupant, next reservation, quick actions
const ROOM_FILTERS = [
{ k: 'all', l: 'Tất cả', ico: null },
{ k: 'occupied', l: 'Đang ở', ico: 'check_circle' },
{ k: 'available', l: 'Còn trống', ico: null },
{ k: 'cleaning', l: 'Cần dọn', ico: 'sparkle' },
{ k: 'maintenance', l: 'Bảo trì', ico: 'settings' },
];
function RoomsPage({ rooms, bookings, guests, today, onOpenBooking, onNewBooking, onRoomAction, onAddRoom, onEditRoom }) {
const data = window.HotelData;
const [filter, setFilter] = React.useState('all');
// Compute effective status per room (considering current bookings)
const enrichedRooms = React.useMemo(() => {
const todayDay = startOfDay(today);
return rooms.map(r => {
// Find current occupant (a non-cancelled booking covering today)
const current = bookings.find(b =>
b.roomId === r.id &&
b.status !== 'cancelled' &&
b.status !== 'maintenance' &&
startOfDay(b.checkIn) <= todayDay &&
startOfDay(b.checkOut) > todayDay
);
// Find next upcoming booking
const upcoming = bookings
.filter(b => b.roomId === r.id && b.status !== 'cancelled' && startOfDay(b.checkIn) > todayDay)
.sort((a, b) => a.checkIn - b.checkIn)[0];
let effectiveStatus = r.status;
if (current) effectiveStatus = 'occupied';
else if (r.status === 'cleaning') effectiveStatus = 'cleaning';
else if (r.status === 'maintenance') effectiveStatus = 'maintenance';
else effectiveStatus = 'available';
// Also handle if maintenance is scheduled by booking
const maintBlock = bookings.find(b =>
b.roomId === r.id &&
b.status === 'maintenance' &&
startOfDay(b.checkIn) <= todayDay &&
startOfDay(b.checkOut) > todayDay
);
if (maintBlock) effectiveStatus = 'maintenance';
return { ...r, effectiveStatus, current, upcoming, maintBlock };
});
}, [rooms, bookings, today]);
// Counts for filters
const counts = React.useMemo(() => {
const c = { all: enrichedRooms.length, occupied: 0, available: 0, cleaning: 0, maintenance: 0 };
enrichedRooms.forEach(r => { c[r.effectiveStatus] = (c[r.effectiveStatus] || 0) + 1; });
return c;
}, [enrichedRooms]);
// Today revenue (rooms occupied)
const todayRevenue = enrichedRooms
.filter(r => r.current)
.reduce((sum, r) => sum + r.price, 0);
const filtered = enrichedRooms.filter(r => filter === 'all' || r.effectiveStatus === filter);
return (
{/* KPIs */}
Tổng doanh thu
{VNDshort(todayRevenue)}đ
Đêm nay · {enrichedRooms.filter(r => r.current).length} phòng đang ở
Đang ở
{counts.occupied}
Lấp đầy {Math.round((counts.occupied / rooms.length) * 100)}%
Sẵn sàng
{counts.available}
Còn nhận khách
Cần dọn
{counts.cleaning}
Housekeeping
Bảo trì
{counts.maintenance}
Tạm khóa
{/* Toolbar */}
{ROOM_FILTERS.map(f => (
))}
{/* Grid */}
{filtered.map(r => (
))}
{filter === 'all' && (
)}
{filtered.length === 0 && filter !== 'all' && (
Không có phòng nào ở trạng thái này.
)}
);
}
function RoomCard({ room, guests, today, onOpenBooking, onNewBooking, onRoomAction, onEditRoom }) {
const r = room;
const guest = r.current ? findGuest(guests, r.current.guestId) : null;
const status = r.effectiveStatus;
const STATUS_META = {
occupied: { l: 'Đang ở', color: 'var(--primary)' },
available: { l: 'Trống', color: 'var(--status-walkin)' },
cleaning: { l: 'Đang dọn', color: 'var(--status-pending)' },
maintenance: { l: 'Bảo trì', color: 'var(--status-maint)' },
};
const sm = STATUS_META[status];
return (
onEditRoom && onEditRoom(r)}>
{/* Banner */}
{/* Subtle line-art background based on type */}
P.{r.number} · Tầng {r.floor}
{sm.l}
{r.name || ('Phòng ' + r.number)}
{/* Body */}
{r.bed}
{r.cap}
{VNDshort(r.price)}đ/đêm
{r.features && r.features.length > 0 && (
{r.features.slice(0, 4).map((f, i) => (
{f}
))}
{r.features.length > 4 && +{r.features.length - 4}}
)}
{/* Current state */}
{status === 'occupied' && guest && (
{ e.stopPropagation(); onOpenBooking(r.current); }}>
{initials(guest.name)}
{guest.name}
Trả phòng {formatDateShort(r.current.checkOut)} · {r.current.checkOutTime}
)}
{status === 'available' && (
Sẵn sàng đón khách
{r.upcoming
? 'Có khách đặt từ ' + formatDateShort(r.upcoming.checkIn)
: 'Chưa có đặt phòng kế tiếp'}
)}
{status === 'cleaning' && (
Housekeeping đang dọn
Dự kiến xong trong ~30 phút
)}
{status === 'maintenance' && (
Đang bảo trì
{r.maintBlock?.note || 'Tạm khóa khỏi hệ thống'}
)}
{/* Footer actions */}
{status === 'available' && (
)}
{status === 'cleaning' && (
)}
{status === 'maintenance' && (
)}
{(status === 'available') && (
)}
{status === 'occupied' && (
)}
);
}
window.RoomsPage = RoomsPage;