# Add Room Type in Hotel – EMS Section (Admin Panel)

## Overview

The **Add Room Type in Hotel** feature lives in the EMS (Event Management System) section of the admin panel. It lets admins add a **room type** (inventory) to a specific **hotel** from the Hotel Management list.

**User path:** Admin Panel → EMS → Hotels (`/ems/hotels`) → per-hotel **"+ Add Room Type"** → modal → submit.

---

## Flow Summary

1. **HotelList** shows global hotels; each global hotel can have one or more competition-linked **Hotel** records.
2. For each related hotel there is a **"+ Add Room Type"** button that sets `selectedHotelForRoom` to that hotel and opens the **AddRoom** modal.
3. **AddRoom** is a modal with: Hotel name (read-only), Room Name, Room Type (Single/Double), Price, Total Rooms, Beds, Amenities, From/To dates.
4. On submit, frontend POSTs to `POST /api/ems/rooms` with the form data; backend creates a **Room** document and **RoomUnit** documents (one per `no_of_rooms`), assigns the active competition, and returns the created room.

---

## 1. Entry point: HotelList (EMS)

**File:** `src/components/ems/HotelList.js`

- **Route:** Under EMS layout, path `hotels` → `<HotelList />` (e.g. `/ems/hotels`).
- **State:** `selectedHotelForRoom` – when set, the AddRoom modal is shown.
- **Button:** For each `relatedHotels` (hotels linked to a global hotel), a button **"+ Add Room Type"** calls `setSelectedHotelForRoom(hotel)`.
- **Modal render:** `{selectedHotelForRoom && <AddRoom selectedHotel={selectedHotelForRoom} onAdded={...} onClose={...} />}`.

Relevant snippet (trigger + modal):

```jsx
// Button per related hotel
<button
  className="btn btn-sm me-2"
  style={{ color: "#000", borderColor: "#000" }}
  onClick={() => setSelectedHotelForRoom(hotel)}
>
  + Add Room Type
</button>

// Modal
{selectedHotelForRoom && (
  <AddRoom
    selectedHotel={selectedHotelForRoom}
    onAdded={() => {
      setSelectedHotelForRoom(null);
      fetchRooms();
    }}
    onClose={() => setSelectedHotelForRoom(null)}
  />
)}
```

---

## 2. AddRoom modal (full component)

**File:** `src/components/ems/AddRoom.js`

- **Props:** `selectedHotel`, `onAdded`, `onClose`.
- **API:** `POST ${API_URL}/ems/rooms` with body: `room_name`, `room_type`, `price`, `no_of_rooms`, `bed_count`, `amenities`, `from_date`, `to_date`, `hotel_id` (array, from `selectedHotel._id`).
- **Dates:** Sent as `YYYY-MM-DD` strings via `pickerDateToKey(form.from_date)` / `pickerDateToKey(form.to_date)`.

Full component (for test context):

```javascript
import React, { useState } from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "../css/AddRoom.css";
import AmenitiesList from "./AmenitiesList";

const pickerDateToKey = (d) => {
  if (!d) return null;
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
};

const AddRoom = ({ selectedHotel, onAdded, onClose }) => {
  const [form, setForm] = useState({
    hotel_id: selectedHotel?._id ? [selectedHotel._id] : [],
    room_name: "",
    room_type: "",
    price: "",
    no_of_rooms: "",
    bed_count: "1",
    amenities: [],
    from_date: null,
    to_date: null,
  });

  const API_URL = process.env.REACT_APP_API_URL;

  const hotelDisplayName =
    selectedHotel?.global_hotel_id?.global_hotel_name ||
    selectedHotel?.global_hotel_name ||
    "N/A";

  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!form.hotel_id.length) {
      alert("Hotel ID is missing!");
      return;
    }

    try {
      const payload = {
        ...form,
        bed_count: Math.max(1, parseInt(form.bed_count, 10) || 1),
        from_date: pickerDateToKey(form.from_date),
        to_date: pickerDateToKey(form.to_date),
      };
      await axios.post(`${API_URL}/ems/rooms`, payload);
      alert("Room added successfully!");
      onAdded();
      onClose();
    } catch (err) {
      console.error("Add Room Error:", err);
      alert(
        "Failed to add room: " +
          (err.response?.data?.message || err.message)
      );
    }
  };

  return (
    <div
      className="modal show d-block"
      style={{ background: "rgba(0,0,0,0.4)" }}
    >
      <div className="modal-dialog modal-md">
        <div className="modal-content">
          <div className="modal-header">
            <h5 className="modal-title">Add Room Type</h5>
            <button className="btn-close" onClick={onClose}></button>
          </div>

          <form onSubmit={handleSubmit}>
            <div className="modal-body add-room-body">
              <div className="mb-3">
                <strong>Hotel Name:</strong> {hotelDisplayName}
              </div>

              <div className="mb-3">
                <label className="form-label">Room Name</label>
                <input
                  type="text"
                  name="room_name"
                  className="form-control"
                  placeholder="Enter room name"
                  value={form.room_name}
                  onChange={handleChange}
                  required
                />
              </div>

              <div className="mb-3">
                <label className="form-label">Room Type</label>
                <select
                  name="room_type"
                  className="form-control"
                  value={form.room_type}
                  onChange={handleChange}
                  required
                >
                  <option value="">Select room type</option>
                  <option value="Single">Single</option>
                  <option value="Double">Double</option>
                </select>
              </div>

              <div className="mb-3">
                <label className="form-label">Price Per Night</label>
                <input
                  type="number"
                  name="price"
                  className="form-control"
                  placeholder="0.00"
                  value={form.price}
                  onChange={handleChange}
                  required
                />
              </div>

              <div className="mb-3">
                <label className="form-label">Total Rooms Available</label>
                <input
                  type="number"
                  name="no_of_rooms"
                  className="form-control"
                  placeholder="Number of rooms"
                  value={form.no_of_rooms}
                  onChange={handleChange}
                  required
                />
              </div>

              <div className="mb-3">
                <label className="form-label">Number of beds</label>
                <input
                  type="number"
                  name="bed_count"
                  className="form-control"
                  placeholder="1"
                  min={1}
                  value={form.bed_count}
                  onChange={handleChange}
                  required
                />
              </div>

              <AmenitiesList
                amenities={form.amenities}
                onChange={(a) => setForm({ ...form, amenities: a })}
              />

              <div className="row">
                <div className="col">
                  <label className="form-label d-block">Available From</label>
                  <DatePicker
                    selected={form.from_date}
                    onChange={(date) =>
                      setForm({ ...form, from_date: date })
                    }
                    className="form-control w-100"
                    dateFormat="dd-MMM-yyyy"
                    placeholderText="Select date"
                    required
                  />
                </div>
                <div className="col">
                  <label className="form-label d-block">Available To</label>
                  <DatePicker
                    selected={form.to_date}
                    onChange={(date) =>
                      setForm({ ...form, to_date: date })
                    }
                    className="form-control w-100"
                    dateFormat="dd-MMM-yyyy"
                    placeholderText="Select date"
                    required
                  />
                </div>
              </div>
            </div>

            <div className="modal-footer">
              <button
                type="submit"
                className="btn"
                style={{ background: "#D9A128", color: "#fff" }}
              >
                Add Room
              </button>
              <button
                type="button"
                className="btn"
                onClick={onClose}
                style={{ background: "#000", color: "#fff" }}
              >
                Cancel
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
};

export default AddRoom;
```

---

## 3. Styling

**File:** `src/components/css/AddRoom.css`  
AddRoom.js imports: `import "../css/AddRoom.css"` (from `ems/` so resolves to `src/components/css/AddRoom.css`).

```css
.add-room-body .form-label {
  font-weight: bold;
}

.add-room-body .form-control {
  background-color: #fff !important;
  border: 1px solid #ced4da !important;
  box-shadow: none !important;
  padding: 3px;
  font-size: 11px;
}

.add-room-body input,
.add-room-body select {
  border-radius: 6px;
}

.modal-content {
  border-radius: 10px;
}
```

---

## 4. Backend: Room creation

**Route:** `POST /api/ems/rooms`  
**File:** `dashboard/Routes/RoomRoutes.js`

- Expects: `hotel_id` (array or single), `room_name`, `room_type`, `price`, `no_of_rooms`, `bed_count`, `amenities`, `from_date`, `to_date` (YYYY-MM-DD).
- Validates `from_date` / `to_date` with `validateLADateKey` (LA timezone).
- Finds active competition (`Competition.findOne({ status: true })`), assigns it to the room.
- Creates **Room** with `from_la_date_key`/`to_la_date_key` and `from_date`/`to_date` (UTC via `laDateKeyToUTC`).
- Creates **RoomUnit** documents: one per `no_of_rooms`, with `room_id`, `hotel_id` (first hotel), `room_label` "1", "2", …

Relevant create handler (excerpt):

```javascript
router.post("/", async (req, res) => {
  try {
    let hotelIds = req.body.hotel_id;
    if (!Array.isArray(hotelIds)) hotelIds = [hotelIds];
    if (!hotelIds || hotelIds.length === 0) {
      return res.status(400).json({ message: "hotel_id is required" });
    }

    const dateCheck = validateRoomDates(req.body);
    if (!dateCheck.valid) {
      return res.status(400).json({ message: `${dateCheck.field}: ${dateCheck.message}` });
    }

    const activeCompetition = await Competition.findOne({ status: true });
    if (!activeCompetition) {
      return res.status(400).json({ message: "No active competition found" });
    }

    const bedCount = Math.max(1, parseInt(req.body.bed_count, 10) || 1);
    const roomData = {
      hotel_id: hotelIds,
      competition_id: [activeCompetition._id],
      room_name: req.body.room_name,
      room_type: req.body.room_type,
      price: req.body.price,
      no_of_rooms: req.body.no_of_rooms,
      bed_count: bedCount,
      amenities: Array.isArray(req.body.amenities) ? req.body.amenities : [],
    };
    if (req.body.from_date) {
      roomData.from_la_date_key = req.body.from_date;
      roomData.from_date = laDateKeyToUTC(req.body.from_date);
    }
    if (req.body.to_date) {
      roomData.to_la_date_key = req.body.to_date;
      roomData.to_date = laDateKeyToUTC(req.body.to_date);
    }

    const newRoom = await Room.create(roomData);

    const unitCount = Math.max(1, parseInt(req.body.no_of_rooms, 10) || 1);
    const hotelRef = hotelIds[0];
    const unitDocs = [];
    for (let i = 1; i <= unitCount; i++) {
      unitDocs.push({ room_id: newRoom._id, hotel_id: hotelRef, room_label: String(i) });
    }
    await RoomUnit.insertMany(unitDocs);

    const populatedRoom = await newRoom.populate([...]);
    res.status(201).json(populatedRoom);
  } catch (error) {
    // ...
  }
});
```

---

## 5. Models

**Room** (`dashboard/models/Room.js`):

- `hotel_id`: array of ObjectIds (ref Hotel), required.
- `competition_id`: array (ref Competition).
- `room_name`, `room_type`, `price`, `no_of_rooms`, `bed_count`, `amenities`, `from_date`, `to_date`, `from_la_date_key`, `to_la_date_key`, timestamps.

**Hotel** (`dashboard/models/Hotel.js`):

- `global_hotel_id` (ref GlobalHotels), `competition_id` (ref Competition), plus competition-specific contact/notes.

---

## 6. API contract for tests

- **Base URL:** `REACT_APP_API_URL` (e.g. `http://localhost:5000/api`).
- **Create room:** `POST /ems/rooms`
  - Body (JSON): `hotel_id` (array of one or more IDs), `room_name`, `room_type` ("Single"|"Double"), `price`, `no_of_rooms`, `bed_count`, `amenities` (array of strings), `from_date`, `to_date` (YYYY-MM-DD).
  - Success: 201, body = created room (populated).
  - Errors: 400 (validation, no active competition, bad dates), 500.

---

## 7. How to run manual / automated tests

### Manual (browser)

1. Start backend and frontend (e.g. `npm start` in project root for CRA; run dashboard server).
2. Log in as admin, go to EMS → Hotels.
3. Pick a global hotel that has at least one related hotel; click **"+ Add Room Type"** for that hotel.
4. Fill: Room Name, Room Type (Single/Double), Price, Total Rooms, Beds, From/To dates (required).
5. Submit; expect success alert and modal to close; new room type appears under "Room Types & Inventory".

### Automated (API)

- Ensure an active competition and at least one hotel exist.
- `POST /api/ems/rooms` with valid body (include a valid `hotel_id` from your DB).
- Assert status 201 and response has `room_name`, `room_type`, `hotel_id`, `no_of_rooms`; then GET room units (or check DB) to verify `no_of_rooms` units were created.

### Unit / integration (frontend)

- Render `AddRoom` with a mock `selectedHotel` (e.g. `{ _id: '...', global_hotel_id: { global_hotel_name: 'Test Hotel' } }`).
- Simulate filling form and submit; mock `axios.post` to `/ems/rooms` and assert it was called with the correct payload (including `from_date`/`to_date` as YYYY-MM-DD and `hotel_id` as array).

---

## 8. Dependencies

- **AddRoom:** `react`, `axios`, `react-datepicker`, `AmenitiesList` (drag-and-drop amenities).
- **Backend:** `Room`, `RoomUnit`, `Competition` models; `validateLADateKey`, `laDateKeyToUTC` from `dashboard/utils/laDateUtils.js`.

---

## 9. Related files (no full code here)

| File | Role |
|------|------|
| `src/components/ems/EditRoom.js` | Edit existing room (same fields, PUT `/ems/rooms/:id`) |
| `src/components/ems/AmenitiesList.js` | Amenities list with add/remove/reorder |
| `src/App.js` | EMS routes: `hotels` → HotelList, `rooms/add-room` → AddRoom (standalone) |
| `dashboard/server.js` | Mounts `app.use('/api/ems/rooms', RoomRoutes)` |
| `dashboard/utils/laDateUtils.js` | Date validation and LA→UTC conversion |

---

## 10. Manual test checklist (Pass / Fail)

Type **pass** or **fail** in the last column after each check.

### Add Room Type

| # | Check | Pass / Fail |
|---|--------|-------------|
| 1 | EMS → Hotels: "+ Add Room Type" opens Add Room modal for the chosen hotel. | |
| 2 | Modal shows: Hotel name, Room Name, Room Type, Price, Total Rooms, Beds, Amenities, From/To dates. | |
| 3 | Submit with all required fields filled → success alert, modal closes, new room appears in list. | |
| 4 | Submit with amenities added → room created and amenities saved (visible in Edit or API/DB). | |
| 5 | Submit with no amenities → room created with empty amenities. | |
| 6 | Cancel / close modal → no room created, modal closes. | |
| 7 | Submit with missing required field → validation prevents submit or error shown. | |
| 8 | Invalid or empty date range → backend returns error or frontend prevents submit. | |
| 8b | **Available To before Available From** (e.g. From 06-Mar-2026, To 03-Feb-2026) → alert "Available To date cannot be before Available From date", submit blocked. | |

### Edit Room (same flow, Edit button on a room)

| # | Check | Pass / Fail |
|---|--------|-------------|
| 9 | Edit opens modal with existing room data (including amenities) pre-filled. | |
| 10 | Update with changed fields (e.g. price, amenities) → room updates, data persisted. | |
| 11 | Update with amenities removed → saved with reduced/empty amenities. | |
| 12 | Cancel Edit → no changes saved. | |

### Amenities (Add & Edit)

| # | Check | Pass / Fail |
|---|--------|-------------|
| 13 | Add amenity (type + Add or Enter) → appears in list; submit → saved. | |
| 14 | Remove amenity (×) before submit → not in payload / not saved. | |
| 15 | Add with only spaces → nothing added (or trimmed). | |
| 16 | Special characters in amenity (e.g. "Wi-Fi", quotes) → saved and displayed correctly. | |

*For more edge cases, see `docs/ROOM_AMENITIES_MANUAL_CHECKLIST.md`.*

---

This document gives full code context for the Add Room Type in Hotel flow so you can run manual and automated tests against it.
