diff --git a/backend/src/routes/rooms.js b/backend/src/routes/rooms.js index 9fddc94..85b5735 100644 --- a/backend/src/routes/rooms.js +++ b/backend/src/routes/rooms.js @@ -18,7 +18,7 @@ function filterBookingData(booking, canSeeGuest, canSeeRate, canSeeAllNotes, vis booking_arrival: booking.booking_arrival, booking_departure: booking.booking_departure, booking_eta: booking.booking_eta, - booking_locked: booking.booking_locked, + booking_locked: booking.booking_locked === true || booking.booking_locked === 1 || booking.booking_locked === '1', pax: booking.pax, site_id: booking.site_id, custom_fields: booking.custom_fields || [], @@ -26,7 +26,7 @@ function filterBookingData(booking, canSeeGuest, canSeeRate, canSeeAllNotes, vis if (canSeeGuest) { const guests = booking.guests || [] - out.guest_name = guests[0]?.guest_name || booking.account_for_name || null + out.guest_name = guests[0]?.guest_name || booking.guest_name || booking.account_for_name || null } if (canSeeRate) { diff --git a/frontend/src/components/FilterBar.tsx b/frontend/src/components/FilterBar.tsx index 799358e..c572609 100644 --- a/frontend/src/components/FilterBar.tsx +++ b/frontend/src/components/FilterBar.tsx @@ -1,6 +1,7 @@ import { useRef } from 'react' import { ChevronLeft, ChevronRight } from 'lucide-react' -import type { FlowType, FilterMode, FilterState, StatFilters, StatFilterMode } from '../types' +import type { FilterMode, FilterState, StatFilters, StatFilterMode, SemanticFlowKey } from '../types' +import { SEMANTIC_FLOW_MAP } from '../types' import type { RoomData } from '../types' interface FilterBarProps { @@ -9,13 +10,13 @@ interface FilterBarProps { rooms: RoomData[] viewDate: string onToggleCategory: (id: string) => void - onToggleFlow: (flow: FlowType) => void + onToggleFlow: (flow: SemanticFlowKey) => void } -const FLOW_TYPES: FlowType[] = ['arrive', 'depart', 'stopover', 'back-to-back', 'vacant', 'blocked'] -const FLOW_LABELS: Record = { - arrive: 'Arriving', depart: 'Departing', stopover: 'Staying', - 'back-to-back': 'B2B', vacant: 'Vacant', blocked: 'Blocked', +const SEMANTIC_KEYS: SemanticFlowKey[] = ['arriving', 'departing', 'back-to-back', 'staying', 'vacant', 'blocked'] +const SEMANTIC_LABELS: Record = { + arriving: 'Arriving', departing: 'Departing', 'back-to-back': 'B2B', + staying: 'Staying', vacant: 'Vacant', blocked: 'Blocked', } function nextMode(mode: FilterMode): FilterMode { @@ -28,8 +29,9 @@ function countByCategory(rooms: RoomData[], catId: string) { return rooms.filter(r => r.category_id === catId).length } -function countByFlow(rooms: RoomData[], flow: FlowType) { - return rooms.filter(r => r.flow_type === flow).length +function countBySemantic(rooms: RoomData[], key: SemanticFlowKey) { + const flowTypes = SEMANTIC_FLOW_MAP[key] + return rooms.filter(r => flowTypes.includes(r.flow_type)).length } export function FilterBar({ filters, categories, rooms, viewDate, onToggleCategory, onToggleFlow }: FilterBarProps) { @@ -70,17 +72,17 @@ export function FilterBar({ filters, categories, rooms, viewDate, onToggleCatego {/* Flow type filter row */}
- {FLOW_TYPES.map(flow => { - const mode = filters.flowTypes[flow] ?? 'off' - const count = countByFlow(rooms, flow) + {SEMANTIC_KEYS.map(key => { + const mode = filters.flowTypes[key] ?? 'off' + const count = countBySemantic(rooms, key) if (count === 0 && mode === 'off') return null return ( ) diff --git a/frontend/src/pages/Planner.tsx b/frontend/src/pages/Planner.tsx index 965f316..62e6dc8 100644 --- a/frontend/src/pages/Planner.tsx +++ b/frontend/src/pages/Planner.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react' import { RefreshCw } from 'lucide-react' -import type { RoomData, AppConfig, Category, FilterState, StatFilters, FlowType, ActivityEntry } from '../types' +import type { RoomData, AppConfig, Category, FilterState, StatFilters, SemanticFlowKey, ActivityEntry } from '../types' +import { SEMANTIC_FLOW_MAP } from '../types' import { useAuth } from '../components/AuthGate' import CategoryGroup from '../components/CategoryGroup' import { FilterBar, StatFilterBar } from '../components/FilterBar' @@ -138,7 +139,7 @@ export default function Planner() { }) }, []) - const toggleFlow = useCallback((flow: FlowType) => { + const toggleFlow = useCallback((flow: SemanticFlowKey) => { setFilters(f => { const cur = f.flowTypes[flow] ?? 'off' const next = cur === 'off' ? 'inclusive' : cur === 'inclusive' ? 'exclusive' : 'off' @@ -158,17 +159,21 @@ export default function Planner() { const visibleRooms = useMemo(() => { const inclCats = Object.entries(filters.categories).filter(([, m]) => m === 'inclusive').map(([k]) => k) const exclCats = Object.entries(filters.categories).filter(([, m]) => m === 'exclusive').map(([k]) => k) - const inclFlows = Object.entries(filters.flowTypes).filter(([, m]) => m === 'inclusive').map(([k]) => k) as FlowType[] - const exclFlows = Object.entries(filters.flowTypes).filter(([, m]) => m === 'exclusive').map(([k]) => k) as FlowType[] + const inclSemantic = Object.entries(filters.flowTypes).filter(([, m]) => m === 'inclusive').map(([k]) => k as SemanticFlowKey) + const exclSemantic = Object.entries(filters.flowTypes).filter(([, m]) => m === 'exclusive').map(([k]) => k as SemanticFlowKey) + + // Expand semantic keys to the union of their real flow types + const inclFlowTypes = new Set(inclSemantic.flatMap(s => SEMANTIC_FLOW_MAP[s])) + const exclFlowTypes = new Set(exclSemantic.flatMap(s => SEMANTIC_FLOW_MAP[s])) return rooms.filter(room => { // Category filters: if any inclusive → must be in inclusive set; exclusive → must not be in exclusive set if (inclCats.length && !inclCats.includes(room.category_id)) return false if (exclCats.includes(room.category_id)) return false - // Flow type filters - if (inclFlows.length && !inclFlows.includes(room.flow_type)) return false - if (exclFlows.includes(room.flow_type)) return false + // Flow filters: semantic — e.g. "Departing" matches both 'depart' and 'back-to-back' + if (inclFlowTypes.size && !inclFlowTypes.has(room.flow_type)) return false + if (exclFlowTypes.has(room.flow_type)) return false // Stat filters const tasks = statFilters.newbookTasks @@ -240,6 +245,7 @@ export default function Planner() { value={viewDate} onChange={e => setViewDate(e.target.value)} /> + {formatDateLabel(viewDate)} {viewDate !== todayStr() && ( diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 1ad644b..6d64c87 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -86,9 +86,22 @@ export interface ActivityEntry { export type FilterMode = 'off' | 'inclusive' | 'exclusive' +// Semantic filter keys — each maps to one or more FlowTypes so that e.g. +// "Departing" catches both 'depart' and 'back-to-back' rooms. +export type SemanticFlowKey = 'arriving' | 'departing' | 'back-to-back' | 'staying' | 'vacant' | 'blocked' + +export const SEMANTIC_FLOW_MAP: Record = { + arriving: ['arrive', 'back-to-back'], + departing: ['depart', 'back-to-back'], + 'back-to-back': ['back-to-back'], + staying: ['stopover'], + vacant: ['vacant'], + blocked: ['blocked'], +} + export interface FilterState { categories: Record - flowTypes: Partial> + flowTypes: Partial> } export type StatFilterMode = 'off' | 'show-only' | 'hide'