# Room Date Range Greying – Plan and Illustrations

This document explains the **Room Date Range Greying** feature: how grid cells outside the room type's booking window (and before "today" in **America/Los_Angeles**) are greyed out and made non-bookable.

---

## 1. Goal

- Each **room type** has `from_date` / `to_date` (UTC instants representing LA midnight) and `from_la_date_key` / `to_la_date_key` (YYYY-MM-DD strings in LA time).
- Example: Deluxe room is available **21 March 2026 – 27 March 2026** (7 days, LA calendar days).
- In the booking grid:
  - **Within that window (and >= today in LA):** cells stay **available** (white, clickable/draggable) or **booked** (green/red).
  - **Outside that window or before today in LA:** cells are **greyed out**, not clickable, and not part of drag selection.

All date logic uses **America/Los_Angeles** for "today" and for comparing with the room's `from_la_date_key` / `to_la_date_key`.

---

## 2. Visual Overview

### 2.1 Grid before the feature (all dates look the same)

Every date column is either "available" (price) or "booked" (name, price, etc.). There is no notion of "outside the room's window."

```
        | 18 Mar | 19 Mar | 20 Mar | 21 Mar | 22 Mar | 23 Mar | 24 Mar | 25 Mar | 26 Mar | 27 Mar | 28 Mar | 29 Mar |
Room 1  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |
Room 2  |  $600  |  John   |  $600  |  $600  |  ...   |  ...   |  ...   |  ...   |  ...   |  ...   |  ...   |  ...   |
```

### 2.2 Grid after the feature (room 21 Mar – 27 Mar, today = 20 Mar LA)

Dates **before** the room window or **before today in LA** are greyed out. Dates **after** the room window are also greyed out. Only 21–27 Mar are bookable.

```
        | 18 Mar | 19 Mar | 20 Mar | 21 Mar | 22 Mar | 23 Mar | 24 Mar | 25 Mar | 26 Mar | 27 Mar | 28 Mar | 29 Mar |
        | grey   | grey   | grey   | OK     | OK     | OK     | OK     | OK     | OK     | OK     | grey   | grey   |
Room 1  |  ░░░   |  ░░░   |  ░░░   |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  $600  |  ░░░   |  ░░░   |
Room 2  |  ░░░   |  ░░░   |  ░░░   |  $600  |  John  |  ...   |  ...   |  ...   |  ...   |  $600  |  ░░░   |  ░░░   |
```

Legend:
- **░** = greyed-out (disabled) cell
- **$600** = available (bookable) cell
- **John** = booked cell (still only in the 21–27 Mar range in this example)

### 2.3 Decision flow for one cell

```
                    ┌─────────────────┐
                    │  Cell date (d)  │
                    └────────┬────────┘
                             │
                             ▼
                    ┌────────────────────────┐
                    │ dk < today (LA)?       │
                    └────────┬───────────────┘
                             │
              ┌──────────────┼──────────────┐
              │ Yes          │ No           │
              ▼              ▼              │
        ┌──────────┐  ┌───────────────────┐│
        │ DISABLED │  │ dk < from_la_key? ││
        │  (grey)  │  └────────┬──────────┘│
        └──────────┘           │            │
                    ┌──────────┼──────────┐ │
                    │ Yes      │ No       │ │
                    ▼          ▼          │ │
              ┌──────────┐ ┌─────────────┐│ │
              │ DISABLED │ │dk>to_la_key?││ │
              └──────────┘ └──────┬──────┘│ │
                                  │       │ │
                          ┌───────┼───────┐ │
                          │ Yes   │ No    │ │
                          ▼       ▼       │ │
                    ┌──────────┐ ┌──────────────┐
                    │ DISABLED │ │ IN RANGE     │
                    └──────────┘ │ (available   │
                                 │  or booked)  │
                                 └─────────────┘
```

---

## 3. Data flow (LA timezone)

All comparisons use **LA date key strings** in `YYYY-MM-DD` form (from `utcToLADateKey(date)` in `src/utils/laDateUtils.js`).

```mermaid
flowchart LR
  subgraph inputs [Inputs]
    today["todayLA\n(todayLAKey())"]
    roomFrom["roomFromKey\nfrom_la_date_key"]
    roomTo["roomToKey\nto_la_date_key"]
    cellDate["cell date\n(date column)"]
  end

  subgraph compare [Comparison]
    dk["utcToLADateKey(cellDate)"]
    check["dk < todayLA?\ndk < roomFromKey?\ndk > roomToKey?"]
  end

  today --> check
  roomFrom --> check
  roomTo --> check
  cellDate --> dk --> check
  check --> result["Out of range?\n→ DisabledCell\nElse → AvailableCell / BookedCell"]
```

- **todayLA:** computed once via `todayLAKey()` so "today" is stable and reflects LA calendar day.
- **roomFromKey / roomToKey:** from the selected room type's `from_la_date_key` and `to_la_date_key` (YYYY-MM-DD strings).
- A cell is **out of range** if: `dk < todayLA` OR `dk < roomFromKey` OR `dk > roomToKey`.

---

## 4. API contract

All hotel date fields use **YYYY-MM-DD** strings representing **LA calendar days**.

- **Frontend sends:** `date`, `from_date`, `to_date` as YYYY-MM-DD.
- **Backend validates:** `/^\d{4}-\d{2}-\d{2}$/` regex + valid LA calendar day.
- **Backend converts:** YYYY-MM-DD to UTC instant (LA midnight) via `laDateKeyToUTC`.
- **Backend stores:** both UTC `date` and `la_date_key` (YYYY-MM-DD).
- **Backend rejects:** ISO timestamps with 400 error.

---

## 5. `la_date_key` – canonical key

- `la_date_key` (String, YYYY-MM-DD) is the canonical identifier for grid cells and occupancy logic.
- **bookingsMap** keys by `la_date_key` from the API response.
- **Duplicate check:** `room_unit_id + la_date_key + participant_id` (unique compound index).
- **sumGuests:** queries by `room_unit_id + la_date_key` equality.

---

## 6. Files and components

### 6.1 Where changes are made

| File | Change |
|------|--------|
| `src/utils/laDateUtils.js` | LA timezone helpers: `todayLAKey`, `utcToLADateKey`, `laDateKeyToUTC`, `laDateRangeToUTCBounds`, `validateLADateKey` |
| `dashboard/utils/laDateUtils.js` | Backend equivalent (same contract, CommonJS) |
| `dashboard/models/Booking.js` | Added `la_date_key` field + compound indexes |
| `dashboard/models/Room.js` | Added `from_la_date_key`, `to_la_date_key` fields |
| `dashboard/Routes/BookingRoutes.js` | Validate + convert YYYY-MM-DD, store `la_date_key`, reject ISO, query by `la_date_key` |
| `dashboard/Routes/RoomRoutes.js` | Validate + convert YYYY-MM-DD, store `from_la_date_key`/`to_la_date_key`, reject ISO |
| `dashboard/Routes/RoomUnitRoutes.js` | Include `from_la_date_key`/`to_la_date_key` in Room populates |
| `src/components/ems/RoomsInformation.js` | LA-based `startDate`, `getDates`, `shiftDates`, `isOutOfRange`, `bookingsMap` key, header formatting |
| `src/components/ems/AllocationModal.js` | Submit `date` as YYYY-MM-DD, checkIn/checkOut as YYYY-MM-DD |
| `src/components/ems/EditRoom.js` | Submit `from_date`/`to_date` as YYYY-MM-DD |
| `src/components/ems/AddRoom.js` | Submit `from_date`/`to_date` as YYYY-MM-DD |
| `src/components/ems/AssignRooms.js` | `isWithinRoomRange` uses `from_la_date_key`/`to_la_date_key` string compare, dates as YYYY-MM-DD |
| `src/components/ems/BookingCellMenu.js` | Display date via `utcToLADateKey` |
| `src/utils/date.js` | Added `formatDateInLA` |
| `src/components/css/RoomsInformation.css` | `.rooms-info-cell--disabled` styling |

### 6.2 Cell render order

In the grid's `dates.map(...)` loop, the order of checks is:

1. **If `isOutOfRange(date)`** — render `<DisabledCell />` (grey, no events).
2. **Else if** the cell has bookings — render `<BookedCell />` (unchanged).
3. **Else** — render `<AvailableCell />` (unchanged, with drag handlers).

So "out of range" overrides everything; within range, existing booked/available behaviour is unchanged.

### 6.3 Drag selection

- **Mouse down:** only on `AvailableCell`, which is never used for out-of-range dates.
- **Mouse up (finalizeDrag):** the selected date range is filtered to drop any date for which `isOutOfRange(d)` is true, so the modal only receives in-range dates.

---

## 7. Edge cases

- **Room has no `from_la_date_key` / `to_la_date_key`:** The only check that applies is `dk < todayLA`, so past dates are grey and future dates are bookable.
- **`to_date` before `from_date`:** Not fixed by this feature; all dates can be considered out of range.
- **Today inside the window:** e.g. today = 24 Mar (LA), window 21–27 Mar — all 21–27 Mar are in range; 18–20 and 28–29 are grey.
- **DST boundary (Spring forward: 2026-03-08):** dayjs with timezone plugin handles the 2am skip correctly. `laDateKeyToUTC("2026-03-08")` returns the correct UTC instant for LA midnight.
- **Browser in UTC+12:** Grid still shows LA calendar days because all date generation and formatting uses `dayjs.tz("America/Los_Angeles")`.

---

## 8. Reference

- Implementation plan: `.cursor/plans/la_timezone_implementation_d84ecedd.plan.md`
- Migration script: `dashboard/scripts/migrate-dates-to-la.js`
