// 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 */} {r.type.startsWith('Suite') ? ( <> ) : r.type === 'Deluxe' ? ( <> ) : ( <> )} 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;