From b528624be013e58cfc6ce75a749b0b616f36c95d Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Sat, 4 Jul 2026 18:33:07 +0000 Subject: [PATCH] =?UTF-8?q?Cards:=20date-aware=20display=20=E2=80=94=20lat?= =?UTF-8?q?e=20checkouts=20only=20on=20future=20days,=20hide=20current-sta?= =?UTF-8?q?te=20info=20off-today?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Future dates: departure time sliver only shows for late checkouts (after default_checkout_time, default 11:00) — on future days every in-house booking is still 'arrived' so the time was showing on every card - Sliver bracket widens to 19px when a time is shown and the time renders vertically (writing-mode) to fit the narrow space - Clean/dirty site status pill only shows when viewing today — it reflects current state, meaningless on other dates - NewBook task pill, task dots, and category header task/dirty badges hidden on future dates Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/CategoryGroup.tsx | 8 +++-- frontend/src/components/RoomCard.tsx | 36 +++++++++++++++-------- frontend/src/index.css | 15 +++++++--- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/CategoryGroup.tsx b/frontend/src/components/CategoryGroup.tsx index 06091ea..832c108 100644 --- a/frontend/src/components/CategoryGroup.tsx +++ b/frontend/src/components/CategoryGroup.tsx @@ -15,6 +15,10 @@ interface Props { export default function CategoryGroup({ categoryId, categoryName, rooms, viewDate, config, onRoomClick }: Props) { const [open, setOpen] = useState(true) + const today = new Date().toISOString().slice(0, 10) + const isToday = viewDate === today + const isFuture = viewDate > today + const outstandingTasks = rooms.reduce((n, r) => { return n + r.tasks.filter(t => { const d = t.task_when_date || t.task_period_from @@ -30,10 +34,10 @@ export default function CategoryGroup({ categoryId, categoryName, rooms, viewDat
setOpen(o => !o)}> {categoryName} {rooms.length} - {outstandingTasks > 0 && ( + {!isFuture && outstandingTasks > 0 && ( {outstandingTasks} tasks )} - {dirtyCount > 0 && ( + {isToday && dirtyCount > 0 && ( {dirtyCount} dirty )} today + // Booking status (lowercased) drives the card border via CSS attribute selector const bookingStatus = booking?.booking_status?.toLowerCase() ?? null @@ -94,9 +98,13 @@ function RoomCard({ room, viewDate, config, onClick }: Props) { // Departure time shown in the left-bracket sliver (not as a card pill). // Show only when there's a departing booking whose status is still 'arrived' // (not yet checked out). Matches original: collapses once status flips to 'departed'. + // On future dates every in-house booking is still 'arrived', so only show + // late checkouts (time after the default checkout time). + const defaultCheckout = (config?.default_checkout_time || '11:00').slice(0, 5) const sliverDepartTime = !room.spans_previous && room.departing_time && - room.departing_booking?.booking_status?.toLowerCase() === 'arrived' + room.departing_booking?.booking_status?.toLowerCase() === 'arrived' && + (!isFuture || room.departing_time.slice(0, 5) > defaultCheckout) ? room.departing_time.slice(0, 5) : null @@ -131,7 +139,7 @@ function RoomCard({ room, viewDate, config, onClick }: Props) { return (
)} onClick={() => onClick(room)} > @@ -139,7 +147,7 @@ function RoomCard({ room, viewDate, config, onClick }: Props) { {sliverDepartTime && (
- {sliverDepartTime} + {sliverDepartTime}
)} @@ -171,9 +179,11 @@ function RoomCard({ room, viewDate, config, onClick }: Props) { {nights.current}/{nights.total} )} - - {room.site_status || 'Unknown'} - + {isToday && ( + + {room.site_status || 'Unknown'} + + )}
@@ -222,16 +232,18 @@ function RoomCard({ room, viewDate, config, onClick }: Props) { )} - {/* Task status */} - - - {outstanding.length > 0 && {outstanding.length}} - + {/* Task status — housekeeping tasks only apply up to today */} + {!isFuture && ( + + + {outstanding.length > 0 && {outstanding.length}} + + )} )} {/* ── show_on_card task dots ──────────────────── */} - {todayTasks.some(t => taskDisp[t.task_type_id]?.show_on_card) && ( + {!isFuture && todayTasks.some(t => taskDisp[t.task_type_id]?.show_on_card) && (
{todayTasks.map(t => { const d = taskDisp[t.task_type_id] diff --git a/frontend/src/index.css b/frontend/src/index.css index b292cca..6cecc58 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -460,23 +460,30 @@ html, body, #root { .room-card[data-next-status="unconfirmed"]::after { border-color: #f59e0b; } .room-card[data-next-status="departed"]::after { border-color: #a855f7; } -/* Departure time shown in left-bracket sliver (while prev booking still 'arrived') */ +/* Departure time shown in left-bracket sliver (while prev booking still 'arrived'). + The bracket widens to 19px when a time is shown; the time reads vertically. */ +.room-card.has-sliver-time[data-previous-status]::before { width: 19px; } .card-sliver-depart { position: absolute; - left: -21px; + left: -23px; + width: 19px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; align-items: center; - gap: 1px; - font-size: 8px; + gap: 2px; + font-size: 9px; font-weight: 600; color: var(--col-arrived); line-height: 1; pointer-events: none; white-space: nowrap; } +.card-sliver-depart .sliver-time { + writing-mode: vertical-rl; + letter-spacing: .04em; +} /* Blocked room */ .room-card.room-blocked {