# Chat Context: Room Booking, DB Safeguards, and UI Changes

**Purpose:** Detailed context from a long chat session so you can continue work in a new conversation. Copy this (or relevant sections) into the new chat for continuity.

**Project:** IGF-EMS (Event Management System). React (CRA) frontend, Express backend, MongoDB/Mongoose. Auth via JWT. Frontend under `src/`, backend under `dashboard/` (routes, models, middleware). Shared API client: `src/api/axiosInstance.js`. Env: `.env` (dev) / `.env.production` (prod); never overwrite `.env` without user confirmation.

---

## 1. Database Safeguards (Orphan References)

**Goal:** Avoid hard-deleting entities that are still referenced by AssignRoom (or Booking).

**Done:**

- **Person DELETE** (`dashboard/Routes/PersonRoutes.js`): Before deleting a person, we check if any **AssignRoom** has that person in `participant_ids`. If yes → **409 Conflict** with message: "Cannot delete person: they are assigned to a room. Remove the room assignment first." Also added a check for **Booking**: if any Booking has `participant_id` equal to this person → 409 "Cannot delete person: they have room bookings. Cancel those bookings first." (If the Booking check was added in a later turn, ensure it exists; otherwise add it.)
- **Room DELETE** (`dashboard/Routes/RoomRoutes.js`): Before deleting a room, we check if any **AssignRoom** has `room_id` = this room. If yes → 409 "Cannot delete room: it is assigned to participants. Remove the assignment first." Also check if any **RoomUnit** (or **Booking** via RoomUnit) references this room; if yes → 409 with an appropriate message.
- **Participant:** Only soft-delete (DELETE sets `is_deleted: true`); no hard delete.
- **RoomUnit / Booking:** If RoomUnit or Booking references exist, Person and Room delete handlers should block as above. RoomUnit has no DELETE in the current plan.

**Files touched:** `PersonRoutes.js` (AssignRoom + optionally Booking), `RoomRoutes.js` (AssignRoom + optionally RoomUnit/Booking).

---

## 2. Participant.is_deleted

**Goal:** Exclude soft-deleted participants (`is_deleted: true`) from participant lists and from the “existing” set when bulk-adding.

**Done:**

- **GET /ems/participants** (list): Already excludes deleted unless `is_deleted=true` is sent in query.
- **GET /ems/participants/photo-approval/list:** Already excludes deleted.
- **POST /ems/participants/bulk-add** (`dashboard/Routes/ParticipantRoutes.js`): When building the set of “existing” participants for a competition, we only consider non-deleted participants: `Participant.find({ competition_id, $or: [{ is_deleted: false }, { is_deleted: { $exists: false } }] })`. This allows re-adding a person after they were soft-deleted.
- **AllocationModal** participant picker: Uses GET /ems/participants (no `is_deleted: true`), so deleted participants do not appear.
- **ParticipantsList, AssignRooms, etc.:** All use the same list API; no change needed.

**File touched:** `dashboard/Routes/ParticipantRoutes.js` (bulk-add existing filter).

---

## 3. RoomUnit.hotel_id Denormalization

**Goal:** Document that RoomUnit.hotel_id (and similar denormalized refs) are set at creation only and not updated when Room.hotel_id changes.

**Done:**

- **docs/PROJECT_CONTEXT.md** §4.3 already states: AssignRoom and any future RoomUnit store hotel_id/room_id at creation; do not sync when Room or Hotel change.
- **dashboard/models/Room.js:** A short JSDoc comment at the top states that if a RoomUnit (or similar) has hotel_id, set it at creation only; see PROJECT_CONTEXT.md §4.3.

No code logic changes; documentation only.

---

## 4. Hotel Room Booking Feature Plan

**Plan file:** `.cursor/plans/hotel_room_booking_feature_f8368920.plan.md` (or under workspace `.cursor/plans/`).

**Important constraint added:** **Preserve the existing UI.** Do not change: (1) HotelList – "Assign Rooms" continues to open the **AssignRooms modal** (do not navigate to /ems/bookings from there); (2) RoomsInformation – keep current layout (hotel dropdown, grid, date nav, cell styling); only the data source changes from mock to RoomUnits/Bookings APIs; (3) AssignRooms modal stays as-is.

**Architecture:** Room (type) → RoomUnit (physical, one per bookable room with editable room_label) → Booking (per room unit, date, participant). Booking model: room_unit_id, date, participant_id (Person), guest_count, allocation_type, price, etc. Room has bed_count; RoomUnit does not (bed count comes from Room via unit.room_id).

**When implementing:** Add delete guards for Person (if referenced by Booking) and Room (if referenced by RoomUnit or Booking). Participant picker must exclude is_deleted; RoomUnit.hotel_id set at creation only.

---

## 5. Rooms Information Grid – Size and Layout

**Reduce module size (fit more on one page):**

- **Plan:** `.cursor/plans/reduce_rooms_grid_module_size_63e97dd4.plan.md`
- **Constraint:** Keep all font sizes and typography unchanged; only reduce dimensions and spacing.
- **Implemented in** `src/components/css/RoomsInformation.css`: Page padding 1rem; top bar margin 0.75rem; hotel select min-width 180px, padding 5px 10px; room header gap 6px, padding-left 10px; nav buttons 24×24px, gap 6px, border-radius 5px; grid wrapper border-radius 8px; table border-spacing 0 4px, min-width 1440px; thead th padding 8px 4px; first column 144px, padding 8px 18px; date columns 108px, padding 6px; date card 88×60px, padding 18px 6px, gap 6px, border-radius 5px; tbody tr height 90px; tbody td 108×90px, padding 6px, border-radius 5px; first column 144×90px, padding 8px 18px; room card padding 5px 10px, gap 3px, border-radius 5px; .rooms-info-cell padding 5px 6px, gap 3px, border-radius 5px; .rooms-info-cell--available padding-top 6px.
- Row/cell height unified at 90px (no 140px vs 106px mismatch).

**Price format:** Changed from "600$" to "$600" everywhere: available cells, booked cells in grid, and in BookingCellMenu. Files: `RoomsInformation.js`, `BookingCellMenu.js`.

**Fixed row height:** So rows with and without bookings have the same height (90px, then 106px, then user asked +10px so 106px; after size-reduction plan it became 90px). Overflow hidden on cells so content does not stretch the row.

---

## 6. Allocation Modal – Single Participant Field

**Goal:** Replace the two controls (Search Participant input + Participant dropdown) with one searchable combobox.

**Done in** `src/components/ems/AllocationModal.js`:

- Single "Participant" input with placeholder "Search by name or select...".
- State: `inputValue` (what user sees / types), `selectedParticipant` (person _id for API), `dropdownOpen`.
- On focus: show dropdown list of participants (filtered by inputValue). Exclude is_deleted and Athletes group (same as before).
- Typing: filters list and clears selection so user can pick again.
- Clicking a list item: sets selectedParticipant and inputValue to that person’s name, closes dropdown. Uses onMouseDown + preventDefault so the click registers before blur.
- Click outside (dropdownRef + mousedown listener): closes dropdown.
- Submit: still sends `participant_id: selectedParticipant` (person _id) in POST body. No backend change.

---

## 7. Allocation Type: "Required" Instead of "Standard"

**Goal:** Use "Required" in the UI and in the API instead of "Standard"; keep green styling for that type.

**Backend:**

- **dashboard/models/Booking.js:** `allocation_type` enum is `['required', 'standard', 'private', 'athlete']`. New bookings use "required"; existing "standard" remains valid for backward compatibility.
- **dashboard/Routes/BookingRoutes.js:** PATCH uses `{ $set: updates }` so partial updates do not replace the whole document (fixes "Failed to update booking" when changing type). `allowed` includes allocation_type (no new field needed for this).

**Frontend:**

- **AllocationModal.js:** Default allocation type "required"; dropdown option value "required", label "Required".
- **RoomsInformation.js BookedCell:** Status text shows "Required" for non-private (instead of "Standard" or allocation_type). Green class remains for non-private, non-athlete (rooms-info-cell--standard).
- **BookingCellMenu.js:** Dropdown option "Required" with value "required"; when opening edit, if allocation_type is "standard" normalize to "required" for the form; display "Type: Required" when allocation_type is "standard" or "required".

---

## 8. Booked Cell Layout – Beds Below Private/Required

**Goal:** Show "Beds: X" on its own line **below** the Private/Required line; no editing of bed count, no backend change.

**Done:**

- **src/components/ems/RoomsInformation.js – BookedCell:** Meta line now shows only "Guests: {booking.guests}". After the status span (Private/Required), added `<span className="rooms-info-cell-beds">Beds: {booking.beds}</span>`. Order: name → team → price → Guests → status (Private/Required) → Beds. Data: `beds` still from `unit.room_id?.bed_count || bedCount` (room type).
- **src/components/css/RoomsInformation.css – .rooms-info-cell-beds:** Typography: Poppins, font-weight 400, font-style normal, font-size 8px, line-height 100%, letter-spacing 0%, color #666. Added margin-top: 2px and display: block so Beds sits on its own line below the status.

**Plan (layout only):** `.cursor/plans/editable_beds_below_private_required_1b7f2dee.plan.md` – no editable bed count, no Booking.bed_count field.

---

## 9. Where Guest Count Is Set and Stored

- **AllocationModal:** "Guests" number input when **creating** a booking; sent as `guest_count` in POST to `/api/ems/room-bookings`. Stored on the new Booking document.
- **BookingCellMenu:** "Guests" in the edit form when **editing** a booking; sent as `guest_count` in PATCH to `/api/ems/room-bookings/:id`. Updates the existing Booking’s guest_count.

Backend: Booking model has `guest_count` (Number, default 1, min 1). BookingRoutes POST and PATCH accept and persist it.

---

## 10. Key File Reference

| Area | Path |
|------|------|
| Room booking grid | `src/components/ems/RoomsInformation.js` |
| Grid styles | `src/components/css/RoomsInformation.css` |
| Create booking modal | `src/components/ems/AllocationModal.js` |
| Edit/cancel booking menu | `src/components/ems/BookingCellMenu.js` |
| Booking model | `dashboard/models/Booking.js` |
| Booking API | `dashboard/Routes/BookingRoutes.js` |
| Room model | `dashboard/models/Room.js` (has bed_count) |
| RoomUnit model | `dashboard/models/RoomUnit.js` (no bed_count; has room_id, hotel_id, room_label) |
| Participant list / bulk-add | `dashboard/Routes/ParticipantRoutes.js` |
| Person delete guard | `dashboard/Routes/PersonRoutes.js` |
| Room delete guard | `dashboard/Routes/RoomRoutes.js` |
| Project context | `docs/PROJECT_CONTEXT.md` |

---

## 11. Intended Design vs Current (Summary)

- **Standard/Required:** Design shows "Required" and green; implemented as allocation_type "required" (or "standard" for legacy), label "Required", green cell.
- **Private:** Red/pink cell, "Private" in red; price in design can stay green; current code may show price in red for private – confirm if design wants price always green.
- **Athlete:** Intended design uses light **yellow** cell background; current uses green cell + yellow name badge. Consider changing athlete cell background to light yellow to match design.
- **Grid:** Room column with room label + pencil + "Beds: X"; date columns; cells available (price) or booked (name, team, price, Guests, Required/Private, Beds). Beds line is below Required/Private per latest change.
- **Participant picker:** Single searchable field (combobox) in AllocationModal; no separate search + dropdown.

---

## 12. Conventions and Rules (from project)

- Prefer simple solutions; avoid duplication; consider dev/test/prod.
- Do not overwrite .env without user confirmation.
- API: res.status(code).json({ message: "..." }); 400 validation, 401 auth, 403 forbidden, 404 not found, 409 conflict, 500 server error; 201 for create.
- Use shared axios instance from src/api/axiosInstance.js for authenticated frontend calls.
- Keep files ~200–300 lines; refactor when larger.

Use this document in a new chat to continue work on room booking, DB safeguards, or related UI/API changes.
