Add a Departing state to the Rooms Accessible View
A guest checking out today but not yet gone was previously lumped in with ordinary stayovers — fully greyed out, no way to tell "the guest is gone in 20 minutes" apart from "the guest isn't leaving at all". Split that out into its own bucket: Backend: classifyBookingStatus() now also recognises 'depart'/'checkedout' statuses (already checked out — room's free regardless of the date window). fetchRoomOccupancy() returns occupied/departing/arriving; an in-house booking whose departure date is today lands in 'departing' instead of 'occupied'. The ?unoccupied=true task filter and /api/occupancy both updated for the new three-way split. Frontend: departing rooms get a light dim (opacity only, no greyscale) instead of the hard grey-out, plus a purple 'Departing' badge — matching the booked=green/occupied=blue/departing=purple colour language hvac already uses for NewBook room states. Chip label breakdown now reads "X free • Y arriving • Z departing • W occupied". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
96674c58c8
commit
8ddb99d2d7
3 changed files with 54 additions and 25 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Plus, RefreshCw, Camera, BedDouble, Layers, ChevronDown, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
|
||||
import { Plus, RefreshCw, Camera, BedDouble, LogOut, Layers, ChevronDown, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
|
||||
import type { Task, Category, Location, AppConfig, TaskStatus, Priority } from '../types'
|
||||
import { STATUS_LABELS, can } from '../types'
|
||||
import { fetchTasks, fetchLocations, fetchConfig, fetchOccupancy } from '../api'
|
||||
|
|
@ -20,6 +20,7 @@ export default function Summary() {
|
|||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [occupiedSiteIds, setOccupiedSiteIds] = useState<Set<string>>(new Set())
|
||||
const [departingSiteIds, setDepartingSiteIds] = useState<Set<string>>(new Set())
|
||||
const [arrivingSiteIds, setArrivingSiteIds] = useState<Set<string>>(new Set())
|
||||
|
||||
const [statusFilter, setStatusFilter] = useState<TaskStatus | null>(null)
|
||||
|
|
@ -70,6 +71,7 @@ export default function Summary() {
|
|||
.then(([t, occ]) => {
|
||||
setTasks(t)
|
||||
setOccupiedSiteIds(occ ? new Set(occ.occupied_site_ids) : new Set())
|
||||
setDepartingSiteIds(occ ? new Set(occ.departing_site_ids) : new Set())
|
||||
setArrivingSiteIds(occ ? new Set(occ.arriving_site_ids) : new Set())
|
||||
setError(null)
|
||||
})
|
||||
|
|
@ -115,17 +117,19 @@ export default function Summary() {
|
|||
return c
|
||||
}, [tasks])
|
||||
|
||||
// Rooms chip counts: occupied = guest in room, arriving = booked today not yet in
|
||||
// Rooms chip counts: occupied = guest staying, departing = guest checking out
|
||||
// today (accessible later), arriving = booked today not yet in
|
||||
const roomsCounts = useMemo(() => {
|
||||
if (!roomsView) return null
|
||||
let occupied = 0, arriving = 0
|
||||
let occupied = 0, departing = 0, arriving = 0
|
||||
for (const t of tasks) {
|
||||
const sid = t.newbook_site_id ? String(t.newbook_site_id) : null
|
||||
if (sid && occupiedSiteIds.has(sid)) occupied++
|
||||
else if (sid && departingSiteIds.has(sid)) departing++
|
||||
else if (sid && arrivingSiteIds.has(sid)) arriving++
|
||||
}
|
||||
return { total: tasks.length, occupied, arriving, free: tasks.length - occupied - arriving }
|
||||
}, [tasks, roomsView, occupiedSiteIds, arrivingSiteIds])
|
||||
return { total: tasks.length, occupied, departing, arriving, free: tasks.length - occupied - departing - arriving }
|
||||
}, [tasks, roomsView, occupiedSiteIds, departingSiteIds, arrivingSiteIds])
|
||||
|
||||
const sortedTasks = useMemo(() => {
|
||||
if (sortMode === 'default') return tasks
|
||||
|
|
@ -226,7 +230,7 @@ export default function Summary() {
|
|||
>
|
||||
<BedDouble size={13} strokeWidth={1.75} />
|
||||
{roomsView && roomsCounts
|
||||
? `Rooms (${roomsCounts.free} free${roomsCounts.arriving ? ` • ${roomsCounts.arriving} arriving` : ''}${roomsCounts.occupied ? ` • ${roomsCounts.occupied} occupied` : ''})`
|
||||
? `Rooms (${roomsCounts.free} free${roomsCounts.arriving ? ` • ${roomsCounts.arriving} arriving` : ''}${roomsCounts.departing ? ` • ${roomsCounts.departing} departing` : ''}${roomsCounts.occupied ? ` • ${roomsCounts.occupied} occupied` : ''})`
|
||||
: 'Rooms Accessible View'}
|
||||
</button>
|
||||
)}
|
||||
|
|
@ -253,18 +257,23 @@ export default function Summary() {
|
|||
const renderTask = (t: Task) => {
|
||||
const sid = t.newbook_site_id ? String(t.newbook_site_id) : null
|
||||
const isOccupied = roomsView && !!sid && occupiedSiteIds.has(sid)
|
||||
const isArriving = roomsView && !!sid && !isOccupied && arrivingSiteIds.has(sid)
|
||||
const isDeparting = roomsView && !!sid && !isOccupied && departingSiteIds.has(sid)
|
||||
const isArriving = roomsView && !!sid && !isOccupied && !isDeparting && arrivingSiteIds.has(sid)
|
||||
return (
|
||||
<div
|
||||
key={t.id}
|
||||
className={`card task-card ${t.priority === 'urgent' ? 'urgent' : t.priority === 'high' ? 'high' : ''} ${t.unusable ? 'unusable' : ''}`}
|
||||
style={isOccupied ? { opacity: 0.5, filter: 'grayscale(0.3)' } : undefined}
|
||||
// Occupied (stayover) rooms grey out hard — not accessible today. Departing
|
||||
// rooms are lightly dimmed only: guest is still in there right now, but the
|
||||
// room frees up later today so it's worth flagging rather than hiding.
|
||||
style={isOccupied ? { opacity: 0.5, filter: 'grayscale(0.3)' } : isDeparting ? { opacity: 0.8 } : undefined}
|
||||
onClick={() => setOpenTask(t.id)}
|
||||
>
|
||||
<div className="task-card-main">
|
||||
<div className="task-card-title">
|
||||
{t.title}
|
||||
{isOccupied && <span className="badge badge-outline"><BedDouble size={10} strokeWidth={1.75} /> Occupied</span>}
|
||||
{isDeparting && <span className="badge" style={{ background: '#ede9fe', color: '#6d28d9', border: '1px solid #c4b5fd' }}><LogOut size={10} strokeWidth={1.75} /> Departing</span>}
|
||||
{isArriving && <span className="badge" style={{ background: '#fef3c7', color: '#92400e', border: '1px solid #fcd34d' }}><BedDouble size={10} strokeWidth={1.75} /> Arrival today</span>}
|
||||
{t.unusable && <UnusableBadge />}
|
||||
{t.template_id && <span className="badge badge-outline">Recurring</span>}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue