Widen booking fetch window, resolve governing booking per-night, add turnover icons/nights to dashboard
Backend:
- fetchBookings() window widened from today-only to yesterday..+7 days, so a
departing booking (arrived any number of nights ago) and upcoming bookings are
both in view — the previous today-only fetch couldn't see a departing guest's
arrival date at all.
- New resolveGoverningBooking(): replaces the single-booking-per-site pick with a
two-tier resolution — (1) any non-cancelled booking whose stay covers today,
preferring one not yet marked 'departed' (so a departing guest keeps governing
blue->purple right up until checkout is confirmed, even with a same-day arrival
also active); (2) otherwise the soonest upcoming booking, purely for colour
('booked' ahead of its own prep window). Also computes booking_kind
(arrival/departure/stayover), current/total night count, and a has_next_booking
turnover hint (for departure days with another booking already lined up).
- determineRoomState() no longer forces 'vacant' for a future-dated arrival — that
case is now handled by resolveGoverningBooking() deciding whether a booking is
relevant to today at all; once picked as governing it always resolves to at
least 'booked'.
- zone_state table: new booking_kind/current_night/total_nights/has_next_booking
columns, persisted every poll alongside room_state and exposed via GET /api/status.
Frontend:
- ZoneCard: room name is now followed by a takeoff (departure) / landing (arrival)
/ bed (stayover) icon plus a current/total nights figure, sourced from the new
status fields. A departure additionally shows a landing icon when has_next_booking,
hinting at a same-day turnover before the room's actually marked departed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
99ec8b9747
commit
48ffebc08d
5 changed files with 138 additions and 37 deletions
|
|
@ -1,9 +1,26 @@
|
|||
import { Thermometer, BatteryLow, TriangleAlert, Snowflake, Flame, Fan, Droplet, Lock, Heater } from 'lucide-react'
|
||||
import { Thermometer, BatteryLow, TriangleAlert, Snowflake, Flame, Fan, Droplet, Lock, Heater, PlaneTakeoff, PlaneLanding, BedDouble } from 'lucide-react'
|
||||
import type { ZoneStatus } from '../types'
|
||||
import { ROOM_STATE_LABELS, MHI_FAN_SPEEDS } from '../types'
|
||||
|
||||
const AC_DEVICE_TYPES = ['mhi_modbus', 'mhi_mqtt', 'midea', 'daikin', 'home_assistant']
|
||||
|
||||
// Departure = takeoff, arrival = landing, stayover = bed (lucide has no "person in
|
||||
// bed" icon, so BedDouble is the closest stand-in). A departure also shows a second
|
||||
// landing icon when has_next_booking — same-day-turnover hint (someone's due in later).
|
||||
function BookingIcons({ zone }: { zone: ZoneStatus }) {
|
||||
if (!zone.booking_kind) return null
|
||||
const iconProps = { size: 13, strokeWidth: 1.75 }
|
||||
return (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 2, marginLeft: 6, fontSize: 11.5, fontWeight: 600, color: 'var(--text-mid)' }}>
|
||||
{zone.booking_kind === 'departure' && <PlaneTakeoff {...iconProps} />}
|
||||
{zone.booking_kind === 'arrival' && <PlaneLanding {...iconProps} />}
|
||||
{zone.booking_kind === 'stayover' && <BedDouble {...iconProps} />}
|
||||
{zone.booking_kind === 'departure' && zone.has_next_booking && <PlaneLanding {...iconProps} />}
|
||||
{zone.total_nights != null && <span>{zone.current_night}/{zone.total_nights}</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const MODE_ICONS: Record<string, typeof Snowflake> = {
|
||||
cool: Snowflake,
|
||||
heat: Flame,
|
||||
|
|
@ -46,6 +63,7 @@ export default function ZoneCard({ zone, onClick }: { zone: ZoneStatus; onClick:
|
|||
<div className="zone-card-title">
|
||||
<span>
|
||||
{zone.name}
|
||||
<BookingIcons zone={zone} />
|
||||
{unhealthy > 0 && (
|
||||
<TriangleAlert
|
||||
size={14}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,21 @@ export interface Zone {
|
|||
device_count?: number
|
||||
}
|
||||
|
||||
// Set alongside room_state from whichever booking currently governs the room (see
|
||||
// resolveGoverningBooking() in backend/src/lib/scheduler.js). NULL when today isn't
|
||||
// actually one of the governing booking's nights yet (e.g. 'booked' days ahead of
|
||||
// its own prep window) or there's no booking at all.
|
||||
export type BookingKind = 'arrival' | 'departure' | 'stayover'
|
||||
|
||||
export interface ZoneStatus extends Zone {
|
||||
room_state: RoomState
|
||||
target_temp: string
|
||||
last_transition_at: string | null
|
||||
last_booking_status: string | null
|
||||
booking_kind: BookingKind | null
|
||||
current_night: number | null
|
||||
total_nights: number | null
|
||||
has_next_booking: boolean
|
||||
devices: DeviceSummary[]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue