Hotel Management Module - Frontend Implementation Specification¶
Overview¶
This document provides detailed technical documentation for the Hotel Management frontend implementation, including the HTML page structure, JavaScript module, state management, and UI interactions.
Files:
- tqweb-adm/hotelmgmt.html (1452 lines) - HTML structure and dialogs
- tqweb-adm/js/modules/hotelmgmt.js (1428 lines) - JavaScript ES6 module
Technology Stack¶
Core Libraries¶
| Library | Version | Purpose |
|---|---|---|
| jQuery | 3.7.1 | DOM manipulation, AJAX calls |
| Foundation | 6.8.1 | CSS framework, UI components |
| Foundation Datepicker | Custom | Date input widgets |
| Notify.js | Custom | Toast notifications |
JavaScript Features Used¶
- ES6 Modules:
import/exportsyntax - Arrow Functions: Concise callback definitions
- Template Literals: String interpolation
- Map Objects: Efficient key-value lookups
- Destructuring: Object and array destructuring
- Promises: Asynchronous API calls via
glb.tlinq()
Page Architecture¶
Layout Structure¶
┌─────────────────────────────────────────────────────────┐
│ HEADER │
│ (Loaded from header.html template) │
└─────────────────────────────────────────────────────────┘
┌──────────────┬──────────────────────────────────────────┐
│ │ │
│ LEFT │ MAIN CONTENT AREA │
│ SIDEBAR │ │
│ │ ┌───────────────────────────────────┐ │
│ Hotel │ │ Hotel Search Panel │ │
│ Search │ │ (Initially visible) │ │
│ Accordion │ └───────────────────────────────────┘ │
│ │ │
│ [Area 1] │ ┌───────────────────────────────────┐ │
│ Hotel A │ │ Hotel Data Tabs │ │
│ Hotel B │ │ (Shown after hotel selection) │ │
│ │ │ │ │
│ [Area 2] │ │ [Hotel Info] [Rooms] [Promos] │ │
│ Hotel C │ │ │ │
│ │ │ Content panels with forms... │ │
│ │ └───────────────────────────────────┘ │
│ │ │
│ │ ┌───────────────────────────────────┐ │
│ │ │ Search Results │ │
│ │ │ (Shown after accommodation │ │
│ │ │ search) │ │
│ │ └───────────────────────────────────┘ │
└──────────────┴──────────────────────────────────────────┘
Modal Dialogs (Overlays)¶
The page uses Foundation Reveal modals for: 1. confirm_dialog - Confirmation prompts 2. hotelmeals-dialog - Meal plan configuration 3. breakdown-dialog - Price breakdown display 4. dialog_small - Room info tooltip 5. newroom-dialog - Add new room form 6. quickcal_dialog - Quick rate periods edit 7. newcal-dialog - Create calendar entries (large form) 8. editcal-dialog - Edit calendar range 9. copycal-dialog - Copy calendar between rooms 10. bookroom-dialog - Create booking request
Module State Management¶
Module-Level State (hotelmgmt.js:7-20)¶
// Entity Maps
let roomMap = new Map(); // roomId → CHotelRoom object
let mealSuppMap = new Map(); // mealPlan → {adlRate:[], chldRate:[]}
let hotelMealPlans = new Map(); // mealPlan → CMealPlan object
let allMealPlans = new Map(); // mealPlan → CMealPlan (all system plans)
let searchResultMap = new Map(); // "hotelId:roomId" → search result
// Current Selections
let selectedHotel; // Currently selected CHotel
let selectedRoom; // Currently selected CHotelRoom
let bookedRoom; // Room selected for booking
// Calendar Data
let roomCalendar = []; // Array of CRoomCalendarEntry
let calStayFromDt; // Calendar editing: stay from date
let calStayToDt; // Calendar editing: stay to date
let calBookFromDt; // Calendar editing: booking from
let calBookToDt; // Calendar editing: booking to
// Lookup Data
let areaList = []; // List of areas (DXB, ADB, etc.)
// UI State
let creatingCalendar = true; // true=creating, false=editing calendar
let hotelSearchParams; // Last hotel search parameters
// Constants
let monthDays = [31,28,31,30,31,30,31,31,30,31,30,31];
let months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'];
State Flow¶
Page Load
↓
listHotels() → Build accordion menu
↓
User clicks hotel
↓
selectHotel() → selectedHotel = hotel
↓
showHotelData() → Load tabs
↓
showHotelRooms() → roomMap populated
↓
updateRoomData() → selectedRoom = room
Core Functions¶
1. Hotel Management Functions¶
listHotels(namepart, area) - hotelmgmt.js:205¶
Purpose: Load and display hotel list in left sidebar accordion
Flow:
export function listHotels(namepart, area) {
const sessId = sessionStorage.getItem("ss_user_uid");
const apiParams = {
"session": sessId,
"name": (namepart ? namepart : null),
"area": (area ? area : null)
};
glb.tlinq("hotel/listHotels", apiParams).then((hotels) => {
if((hotels) && (hotels.length > 0)) {
// Group hotels by area
let hotelMap = new Map();
hotels.forEach((hotel) => {
let areaHotels = hotelMap.has(hotel.area) ?
hotelMap.get(hotel.area) : [];
areaHotels.push(hotel);
hotelMap.set(hotel.area, areaHotels);
});
// Build accordion UI
buildHotelMenu(hotelMap);
} else {
// Show "no hotels" message with "Add First Hotel" link
showMessageSection('warning', msgTxt, '#div-msg-req');
}
// Load all meal plans for later use
glb.tlinq("hotel/listMealPlans", {"session": sessId}).then((plans) => {
plans.forEach((mplan) => {
allMealPlans.set(mplan.plan, mplan);
// Build meal plan checkboxes for meal config dialog
});
});
});
}
UI Result: Accordion menu grouped by area (DXB, ADB, SHJ, etc.), each area containing hotel buttons
selectHotel(event) - hotelmgmt.js:147¶
Purpose: Handle hotel selection from accordion menu
Implementation:
function selectHotel(event) {
const idStr = $(event.currentTarget).attr('data-row-id');
const id = parseInt(idStr);
const sess = sessionStorage.getItem("ss_user_uid");
glb.tlinq("hotel/getHotel", {"session": sess, "id": id}).then((htl) => {
selectedHotel = htl;
showHotelData();
});
}
Triggered By: Click on hotel button in accordion
Updates: selectedHotel state
Next Action: Calls showHotelData()
showHotelData() - hotelmgmt.js:130¶
Purpose: Display hotel details in tabbed interface
Implementation:
function showHotelData() {
// Bind hotel data to Hotel Info tab
let entities = {"hotel": selectedHotel};
setViewEntities($('#hotel-panel'), entities);
// Load rooms for Rooms tab
showHotelRooms();
// Load meal plans for this hotel
const sessId = sessionStorage.getItem("ss_user_uid");
glb.tlinq("hotel/listMealPlans",
{"session": sessId, "hotelId": selectedHotel.hotelId}).then((mls) => {
updateMealPlans(mls);
});
// Hide search results, show hotel data tabs
$('#searchres_div').addClass('hide');
$('#hoteldata_div').removeClass('hide');
}
UI Changes: - Hides search results panel - Shows tabbed interface (Hotel Info | Rooms | Promotions) - Populates Hotel Info tab with form fields bound to hotel entity
updateHotel(event) - hotelmgmt.js:300¶
Purpose: Save hotel changes (create or update)
Implementation:
export function updateHotel(event) {
hideMessageSection('#div-msg-req');
// Get form data using data-entity-name/field attributes
let htlEnt = getViewEntities($('#hotel-panel'))['hotel'];
let htlUpdEnt = selectedHotel;
const isNew = !!!selectedHotel['hotelId'];
// Merge form changes into selectedHotel
for(const cc in htlEnt)
htlUpdEnt[cc] = htlEnt[cc];
const apiParam = {
'session': sessionStorage.getItem("ss_user_uid"),
'hotel': htlUpdEnt
};
glb.tlinq("hotel/saveHotel", apiParam).then((res) => {
selectedHotel = res;
if(isNew)
listHotels(); // Refresh hotel list
$('#updatehotel_btn').notify("Update successful.", "success");
});
}
Data Binding: Uses data-entity-name="hotel" and data-entity-field="fieldName" attributes on form inputs
Notification: Shows toast message at button location
addNewHotel(event) - hotelmgmt.js:325¶
Purpose: Initialize UI for creating new hotel
Implementation:
export function addNewHotel(event) {
hideMessageSection('#div-msg-req');
selectedHotel = {}; // Empty object
$('#hoteldata_div').removeClass('hide');
clearViewEntities($('#hotel-panel')); // Clear all form fields
$('#f_hotelcountry').val("UAE"); // Set default country
}
Note: Clearing selectedHotel.hotelId triggers "create" mode in updateHotel()
2. Room Management Functions¶
showHotelRooms(selRoomId) - hotelmgmt.js:82¶
Purpose: Load room list for selected hotel
Implementation:
function showHotelRooms(selRoomId) {
clearViewEntities($('#rooms-panel'));
const sess = sessionStorage.getItem("ss_user_uid");
roomCalendar = []; // Reset calendar data
glb.tlinq('hotel/listHotelRooms',
{"session": sess, "hotelId": selectedHotel.hotelId}).then((rms) => {
if(rms.length > 0) {
rms.sort((a, b) => a.sortorder - b.sortorder);
setupRoomData(rms, selRoomId);
} else {
$('#f_roomselect').empty()
.notify("No rooms configured for this hotel - Add rooms!", "error");
}
});
}
Flow: API call → setupRoomData() → updateRoomData()
setupRoomData(rooms, selRoomId) - hotelmgmt.js:64¶
Purpose: Populate room dropdown and room map
Implementation:
function setupRoomData(rooms, selRoomId) {
roomMap.clear();
hideMessageSection('#div-msg-room');
const rmSelect = $('#f_roomselect');
rmSelect.empty();
rooms.forEach((room) => {
rmSelect.append(optElem(room.roomId, room.name));
roomMap.set(room.roomId, room);
});
if(selRoomId)
rmSelect.val(selRoomId);
$('#roomcalendar_div').addClass('hide');
updateRoomData();
// Attach change handler
rmSelect.on("change", updateRoomData);
}
Key Feature: roomMap enables fast room lookups by ID
updateRoomData() - hotelmgmt.js:28¶
Purpose: Update UI when room selection changes
Implementation:
function updateRoomData() {
const selRoomId = $('#f_roomselect').val();
const id = parseInt(selRoomId);
const theRoom = roomMap.get(id);
selectedRoom = theRoom;
// Bind room entity to form fields
setViewEntities($('#rooms-panel'), {"room": theRoom});
// Refresh calendar if data exists
if(roomCalendar && (roomCalendar.length > 0)) {
showRoomCalendarClick();
}
// Populate "Copy From" dropdown (all rooms except selected)
const copySelect = $('#f_copyfrom').empty();
roomMap.forEach((rm) => {
if(rm.roomId !== id) {
copySelect.append(optElem(rm.roomId, rm.name));
}
});
}
Triggered By: - Room dropdown change event - After creating new room - After loading rooms
saveRoom(event) - hotelmgmt.js:374¶
Purpose: Update existing room
Implementation:
export function saveRoom(event) {
let updRoom = getViewEntities($('#rooms-panel'))['room'];
for(const fld in updRoom)
selectedRoom[fld] = updRoom[fld];
const sess = sessionStorage.getItem("ss_user_uid");
const apiParam = {"session": sess, "room": selectedRoom};
glb.tlinq("hotel/saveRoom", apiParam).then((res) => {
console.log("Updated room " + res.roomId);
$('#saveroom_btn').notify("Update successful.", "success");
});
}
addNewRoom(event) / saveNewRoom(event) - hotelmgmt.js:369, 349¶
Purpose: Create new room via dialog
Flow:
// Open dialog
export function addNewRoom(event) {
clearViewEntities($('#newroom-panel'));
$('#newroom-dialog').foundation('open');
}
// Save from dialog
export function saveNewRoom(event) {
let newRm = getViewEntities($('#newroom-panel'))['room'];
newRm['hotelId'] = selectedHotel.hotelId;
const sess = sessionStorage.getItem("ss_user_uid");
const apiParam = {"session": sess, "room": newRm};
$('#newroom-dialog').foundation('close');
glb.tlinq("hotel/saveRoom", apiParam).then((res) => {
console.log("Added hotel room " + res.roomId);
showHotelRooms(res.roomId); // Refresh rooms, select new room
$('#savenewroom_btn').notify("Update successful.", "success");
$('#roomcal_placeholder').empty();
$('#roomcalendar_div').addClass('hide');
});
}
Dialog Structure (hotelmgmt.html:77-141): - Room name (text) - Description (text) - Max occupancy (number) - Max adults (number) - Extra bed available (checkbox) - Extra bed required (checkbox) - Room notes (textarea) - Bedding description (textarea)
3. Calendar Management Functions¶
addPricing(event) - hotelmgmt.js:390¶
Purpose: Open calendar creation dialog
Implementation:
export function addPricing(event) {
$('#f_calmealsupp').val('');
$('#f_calmsg').empty();
creatingCalendar = true; // Flag for create mode
$('#wait-div-cal').addClass('hide');
// Clean up meal supplements map (remove obsolete plans)
if((mealSuppMap) && (mealSuppMap.size > 0)) {
let newSm = new Map();
mealSuppMap.forEach((val, key) => {
if(hotelMealPlans.has(key))
newSm.set(key, val);
});
mealSuppMap = newSm;
} else {
mealSuppMap = new Map();
}
$('#newcal-dialog').foundation('open');
}
UI State: Sets creatingCalendar = true to differentiate from editing mode
saveRoomCalendar(event) - hotelmgmt.js:455¶
Purpose: Create calendar entries for date range
Implementation (simplified):
export function saveRoomCalendar(event) {
const calEntity = getViewEntities($('#newcal-panel'))['calendar'];
// Extract dates from date pickers
const fromDate = $('#f_newcalfrom').fdatepicker().data("datepicker").getDate();
const toDate = $('#f_newcalto').fdatepicker().data("datepicker").getDate();
const bookFrom = $('#f_newbkfrom').fdatepicker().data("datepicker").getDate();
const bookTo = $('#f_newbkto').fdatepicker().data("datepicker").getDate();
// Build calendar entry template
let calEnt = {
roomId: selectedRoom.roomId,
market: calEntity.market,
stayDate: glb.webDate(fromDate),
bookFrom: glb.webDate(bookFrom),
bookTo: glb.webDate(bookTo),
baseRate: parseFloat(calEntity.baseRate),
specDayRate: parseFloat(calEntity.specDayRate),
sdRateType: calEntity.sdRateType,
adultRate: parseFloat(calEntity.adultRate),
adultRateType: calEntity.adultRateType,
childRate: parseFloat(calEntity.childRate),
childRateType: calEntity.childRateType,
extraBedRate: parseFloat(calEntity.extraBedRate),
rateBase: calEntity.rateBase,
mealBase: parseInt(calEntity.mealBase),
mealSupplements: buildMealSupplementsJSON(), // From meal supp map
stopSale: roomStatus === 'S',
available: roomStatus === 'A',
onRequest: roomStatus === 'R',
mlos: parseInt(calEntity.mlos),
sdmlos: parseInt(calEntity.sdmlos)
};
const apiParam = {
"session": sessionStorage.getItem("ss_user_uid"),
"template": calEnt,
"stayTo": glb.webDate(toDate),
"specDays": specDaySetting // "WE"/"ALL"/"NO"
};
glb.tlinq("hotel/createRoomCalendar", apiParam).then((calRes) => {
const numRecs = calRes.length;
$('#addcal_btn').notify("Created " + numRecs + " calendar records", "success");
$('#newcal-dialog').foundation('close');
refreshRoomCalendar(); // Reload calendar view
});
}
Key Logic:
- buildMealSupplementsJSON(): Serializes mealSuppMap to JSON array
- Special day setting: "WE" (weekends), "ALL" (all days), "NO" (none)
- Date format conversion: JavaScript Date → "dd-mm-yyyy" string
refreshHotelCalendar(event) - hotelmgmt.js:1161¶
Purpose: Display monthly calendar grid for all rooms
Implementation:
export function refreshHotelCalendar(event) {
const mthStr = $('#f_calmonth').val();
const yrStr = $('#f_calyear').val();
let mth = parseInt(mthStr);
let yr = parseInt(yrStr);
// Calculate date range for selected month
let fromDate = new Date(yr, mth, 1);
let mth2 = 1 + mth;
let yr2 = yr;
if(mth2 > 11) {
yr2++;
mth2 = 0;
}
let toDate = new Date(yr2, mth2, 1);
const fromDateStr = glb.webDate(fromDate);
const toDateStr = glb.webDate(toDate);
const session = sessionStorage.getItem("ss_user_uid");
const mkt = $('#f_hcal_market').val();
const apiParams = {
"session": session,
"hotelId": selectedHotel.hotelId,
"market": mkt,
"fromDate": fromDateStr,
"toDate": toDateStr
};
showWaitDialog('small', '#wait-div-htlcal');
glb.tlinq("hotel/listRoomCalendar", apiParams).then((cal) => {
// Group calendar entries by room
let calRoomMap = new Map();
cal.forEach((cd) => {
let roomCal = calRoomMap.get(cd.roomId);
if(roomCal)
roomCal.entries.push(cd);
else {
roomCal = {
"roomId": cd.roomId,
"entries": [cd]
};
calRoomMap.set(cd.roomId, roomCal);
}
});
// Build calendar table
buildCalendarTable(calRoomMap);
hideWaitDialog();
});
}
Calendar Visualization: - One row per room - Columns 1-31 for each day of month - Color coding: - Green (#3adb76): Normal day available - Yellow (#fff3d9): Special day available - Red (#ff8378): Stop sale - Blue (#6bd7f2): On request - Hover tooltip shows full rate details
buildCalendarTable(calRoomMap) - Referenced in code¶
Purpose: Generate HTML table showing calendar grid
Logic (hotelmgmt.js:1095-1159):
function buildHotelCalCell(aDay, aDate) {
let res = ['', '', ''];
// Determine background color based on status
let bgcol = aDay.specialDay ? '#fff3d9' : '#3adb76';
if(aDay.stopSale)
bgcol = '#ff8378';
else if(aDay.onRequest)
bgcol = '#6bd7f2';
// Get display price
let thePrice = aDay.baseRate.toFixed();
if(aDay.specialDay)
if(aDay.specDayRate)
thePrice = aDay.specDayRate.toFixed();
// Build hover text with all rate details
let hoverText = glb.displayDate(aDate);
hoverText += '\nMLOS:' + aDay.mlos + ' nights';
hoverText += '\n---- Supplements ----';
hoverText += '\nExtra Adlt: ' + (aDay.adultRate ? aDay.adultRate : '0') +
'/' + (aDay.adultSdRate ? aDay.adultSdRate : '0') + ' (' + aDay.adultRateType + ')';
hoverText += '\nExtra Chld: ' + (aDay.childRate ? aDay.childRate : '0') +
'/' + (aDay.childSdRate ? aDay.childSdRate : '0') + ' (' + aDay.childRateType + ')';
// Parse meal supplements JSON
const mealSuppArray = JSON.parse(aDay.mealSupplements);
const msDescArray = [];
mealSuppArray.forEach((ms) => {
const msDesc = ms.mealPlan + ': AD ' + ms.adlRate[0] + '/' + ms.adlRate[1] +
', CH ' + ms.chldRate[0] + '/' + ms.chldRate[1];
msDescArray.push(msDesc);
});
const msDesc = msDescArray.join('\n');
hoverText += '\n' + msDesc;
// Create tooltip span
let rateText = '<span data-tooltip class="top" title="' + hoverText + '">' + thePrice + '</span>';
res[0] = bgcol; // Background color
res[1] = '#0425a8'; // Foreground color
res[2] = rateText; // Cell content HTML
return res;
}
Result: Rich calendar grid with tooltips showing complete pricing details on hover
4. Meal Plan Management¶
editMeals(event) - hotelmgmt.js:251¶
Purpose: Open meal plan configuration dialog
Implementation:
export function editMeals(event) {
// Reset all checkboxes and inputs
$('#td-allmeals').find("[type=checkbox]").prop("checked", false);
$('#td-allmeals').find("[type=number]").val("0");
// Pre-fill with hotel's current meal plans
hotelMealPlans.forEach((mpl) => {
const cbid = '#ckbx-' + mpl.plan;
const adcId = '#adc-' + mpl.plan;
const chcId = '#chc-' + mpl.plan;
$(cbid).prop("checked", true);
$(adcId).val(mpl.adultCost ? mpl.adultCost.toString() : "0");
$(chcId).val(mpl.childCost ? mpl.childCost.toString() : "0");
});
$('#hotelmeals-dialog').foundation("open");
}
Dialog Content (hotelmgmt.html:42-59): - Table with rows for each meal plan (RO, BB, HB, FB, AI, etc.) - Checkbox to enable/disable plan - Adult cost input - Child cost input
saveMeals(event) - hotelmgmt.js:265¶
Purpose: Save meal plan configuration for hotel
Implementation:
export function saveMeals(event) {
let planIds = [];
// Collect checked meal plans with costs
$('#td-allmeals').find("[type=checkbox]:checked").each(function() {
const idStr = $(this).attr("id");
const pl = idStr.split('-')[1]; // Extract plan code (e.g., "BB")
const adcid = '#adc-' + pl;
const chcid = '#chc-' + pl;
const plid = allMealPlans.get(pl).planId;
let vs = $(adcid).val();
const adc = parseFloat(vs ? vs : "0.0");
vs = $(chcid).val();
const chc = parseFloat(vs ? vs : "0.0");
const thePlan = {
mealPlanId: plid,
adultSupplement: adc,
childSupplement: chc
};
planIds.push(thePlan);
});
const apiParams = {
"session": sessionStorage.getItem("ss_user_uid"),
"hotelId": selectedHotel.hotelId,
"plans": planIds
};
glb.tlinq('hotel/updateMealPlans', apiParams).then((newPlans) => {
updateMealPlans(newPlans); // Refresh dropdown
$('#editmeals_btn').notify("Update successful", "success");
});
$('#hotelmeals-dialog').foundation("close");
}
5. Search and Booking Functions¶
searchAllHotels() - hotelmgmt.js:1038¶
Purpose: Search for available rooms across all hotels
Implementation:
export function searchAllHotels() {
const adl = parseInt($('#f_srch_adlts').val());
const chl = parseInt($('#f_srch_chld').val());
const ckin = $('#f_srch_chkin').val();
const ckout = $('#f_srch_chkout').val();
const bgt = $('#f_srch_budget').val();
const mkt = $('#f_srch_market').val();
const sess = sessionStorage.getItem("ss_user_uid");
const apiParam = {
"session": sess,
"adults": adl,
"children": chl,
"checkin": ckin,
"checkout": ckout,
"maxBudget": bgt,
"market": mkt,
"meals": null,
"area": null,
"context": "config" // Employee pricing view
};
// Add optional filters
const harea = $('#f_srch_area').val();
if(harea !== 'ANY')
apiParam.area = harea;
const mls = $('#f_srch_meal').val();
if("NA" !== mls)
apiParam.meals = mls;
hotelSearchParams = apiParam; // Save for booking later
hideMessageSection('#div-msg-req');
showWaitDialog();
glb.tlinq('hotel/searchAccommodation', apiParam).then((hres) => {
if(hres.results && (hres.results.length > 0)) {
// Sort rooms by price within each hotel
hres.results.forEach((res) => {
res.rooms.sort((a, b) => a.price - b.price);
});
// Sort hotels by cheapest room price
hres.results.sort((a, b) => a.rooms[0].price - b.rooms[0].price);
showSearchResultList2(hres);
hideWaitDialog();
} else {
hideWaitDialog();
$.notify("No results found. Please change your search criteria.", "warn");
}
});
}
Search Form (in hotelmgmt.html): - Check-in / check-out dates - Adults / children counts - Area filter (dropdown) - Market (UAE/GCC/ROW) - Meal plan filter - Max budget
showSearchResultList2(sres) - hotelmgmt.js:898¶
Purpose: Display search results as accordion with room tables
Implementation (simplified):
function showSearchResultList2(sres) {
const srdiv = $('#searchres_div');
srdiv.empty();
searchResultMap.clear();
const sracc = $('<ul id="searchres_acc" class="accordion" data-accordion data-allow-all-closed="true"></ul>');
const mgn = 1 + parseInt($('#f_srch_margin').val()) / 100; // Margin multiplier
sres.results.forEach((hotel) => {
const firstRmCost = hotel.rooms[0].price;
const firstRoomPrice = firstRmCost * mgn;
// Create accordion item for hotel
const hotelacc_str = '<li class="accordion-item" data-accordion-item>' +
'<a href="#" class="accordion-title" style="font-size: 110%">' +
hotel.hotel.name + ' <b>(from AED ' + firstRoomPrice.toFixed(2) + ')</b></a>';
const hotelacc = $(hotelacc_str);
// Create table for rooms
const hotelTbl = $('<table></table>');
const hdr = $('<thead><tr><th><p><b>Room type</b></p></th>' +
'<th>Meal</th><th>Cost</th><th>Price</th><th>MLS</th><th>Actions</th>' +
'</tr></thead>');
const body = $('<tbody></tbody>');
hotel.rooms.forEach((room) => {
const rmSellPrc = room.price * mgn;
// Build room description with status indicators
let rmDesc = room.roomName;
if(room.notes)
rmDesc += '<br><span style="font-size: 75%">*' + room.notes + '</span>';
if(room.stopSale)
rmDesc += '<br><span style="font-size: 75%; color: darkred">*Room on stop sale within this period</span>';
else if(room.onRequest)
rmDesc += '<br><span style="font-size: 75%; color: #1779ba">*Room available on request only</span>';
// Create action links
const lnkdata = '' + hotel.hotel.hotelId + ':' + room.roomId;
const bkdlink = '<a class="button perun-button-small" href="#" data-result-id="' + lnkdata + '">Breakdown</a>' +
' <a class="button perun-button-small" href="#" data-book-id="' + lnkdata + '">Book</a>';
// MLOS warning if search is shorter than minimum
const mls = (room.calendar.length < room.mlos ?
'<span style="color:red"><b>' + room.mlos + '</b></span>' : '' + room.mlos);
const rmRowS = '<tr><td>' + rmDesc + '</td><td>' + room.mealPlanName + '</td>' +
'<td>' + room.price.toFixed(2) + '</td>' +
'<td>' + rmSellPrc.toFixed(2) + '</td>' +
'<td>' + mls + '</td>' +
'<td>' + bkdlink + '</td>' +
'</tr>';
body.append($(rmRowS));
searchResultMap.set(lnkdata, room); // Save for breakdown/booking
});
hotelTbl.append(hdr);
hotelTbl.append(body);
const hotelBody = $('<div class="accordion-content" data-tab-content></div>');
hotelBody.append(hotelTbl);
hotelacc.append(hotelBody);
sracc.append(hotelacc);
});
srdiv.append(sracc);
srdiv.foundation(); // Initialize Foundation accordion
$('#hoteldata_div').addClass('hide');
srdiv.removeClass('hide');
// Attach event handlers
$('a[data-result-id]').on("click", showPriceBreakdown);
$('a[data-book-id]').on("click", showBookDialog);
}
Result: Accordion with hotel panels, each containing a table of available rooms with cost, price, and action buttons
showPriceBreakdown(event) - hotelmgmt.js:733¶
Purpose: Display detailed nightly cost breakdown
Implementation:
export function showPriceBreakdown(event) {
const idStr = $(event.currentTarget).attr('data-result-id');
const room = searchResultMap.get(idStr);
const htl = room.hotel;
$('#para_bkdownheader').html('<b>Room: </b>' + room.roomName + ' at ' + htl.hotel.name);
const bkdownBody = $('#breakdown-body');
bkdownBody.empty();
// Display each night's breakdown
room.calendar.forEach((night, idx) => {
const stayDt = new Date();
stayDt.setTime(night.stayDate);
const dtStr = glb.displayDate(stayDt);
const bkdnTxt = night.breakdown; // Array of breakdown strings from API
const bkdnHtml = bkdnTxt.join('<br>');
const rowStr = '<tr><td>' + dtStr + '</td><td>' + bkdnHtml + '</td></tr>';
bkdownBody.append($(rowStr));
});
$('#breakdown-dialog').foundation('open');
}
Breakdown Display: - Date - Base rate - Extra adult charges - Extra child charges - Meal supplements - Extra bed charges - Night total
createBooking(event) / createLead(bookedRoom) - hotelmgmt.js:865, 772¶
Purpose: Create booking request (lead) for selected room
Implementation:
export function createBooking(event) {
createLead(bookedRoom);
}
function createLead(aRoom) {
const fromDate = $('#fl_bk_ckin').fdatepicker().data("datepicker").getDate();
const toDate = $('#fl_bk_ckout').fdatepicker().data("datepicker").getDate();
const qty = parseInt($('#fl_bk_qty').val());
const mgn = 1 + parseInt($('#f_srch_margin').val()) / 100;
const total = qty * (aRoom.price * mgn);
const custName = $('#fl_bk_name').val();
// Build description text
const descr = 'Guest name: ' + custName + '\n' +
'Room type: ' + aRoom.roomName + ', ' +
'Meal plan: ' + allMealPlans.get(aRoom.mealPlanName).name + '\n' +
hotelSearchParams.adults + ' adults\n' +
hotelSearchParams.children + ' children (' + $('#fl_bk_chldages').val() + ')\n' +
'Check-In: ' + glb.displayDate(fromDate) + '\n' +
'Check-Out: ' + glb.displayDate(toDate) + '\n' +
'Number of rooms: ' + qty + '\n\n' +
'Other requests: \n' + $('#fl_bk_otherinfo').val();
const leadReq = {
"session": sessionStorage.getItem("ss_user_uid"),
"tripRequest": {
"customerName": custName,
"tripName": custName + "," + dest,
"description": descr,
"requestCode": glb.createId("TR"),
"origin": "UAE",
"destination": dest,
"startDate": fromDate,
"endDate": toDate,
"numAdults": hotelSearchParams.adults,
"numChildren": hotelSearchParams.children,
"childAges": $('#fl_bk_chldages').val(),
"contactName": custName,
"contactEmail": $('#fl_bk_email').val(),
"contactType": "EMAIL",
"quantity": qty,
"plannedRevenue": total
}
};
$('#wait_bookreq').removeClass('hide');
glb.tlinq('hotel/createBookingRequest', leadReq).then((newTrip) => {
hideMessageSection('#div-msg-req');
sessionStorage.setItem("ss_triprequest", JSON.stringify(newTrip));
$('#wait_bookreq').addClass('hide');
$('#bookroom-dialog').foundation('close');
$.notify('New reservation request created.', "success", {position: "top center"});
});
}
Booking Dialog (hotelmgmt.html): - Hotel name (display only) - Room type (display only) - Check-in / check-out (editable dates) - Number of rooms - Customer name - Customer email - Child ages - Other requests (textarea)
Data Binding System¶
Entity-Field Binding¶
The application uses a custom data binding system using HTML attributes:
setViewEntities(container, entities)¶
Purpose: Populate form fields from entity objects
Usage:
Logic:
1. Find all elements with data-entity-name attribute within container
2. For each element, read data-entity-field attribute
3. Get value from entity object: entity[fieldName]
4. Set input value based on type:
- Text/number inputs: .val(value)
- Checkboxes: .prop("checked", value)
- Selects: .val(value)
getViewEntities(container)¶
Purpose: Extract form field values into entity objects
Usage:
Logic:
1. Find all elements with data-entity-name attribute
2. Group by entity name
3. For each element, extract value:
- Text/number inputs: .val()
- Checkboxes: .is(":checked")
- Selects: .val()
4. Build object: {entityName: {field1: value1, field2: value2, ...}}
Return: Object with entity names as keys
UI Component Patterns¶
1. Foundation Accordion¶
Usage: Hotel list grouped by area
HTML Structure:
<ul class="accordion" data-accordion>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Dubai (DXB)</a>
<div class="accordion-content" data-tab-content>
<button>Atlantis The Palm</button>
<button>Burj Al Arab</button>
</div>
</li>
<li class="accordion-item" data-accordion-item>
<a href="#" class="accordion-title">Abu Dhabi (ADB)</a>
<div class="accordion-content" data-tab-content>
<button>Emirates Palace</button>
</div>
</li>
</ul>
Initialization:
2. Foundation Tabs¶
Usage: Hotel Info | Rooms | Promotions tabs
HTML Structure:
<ul class="tabs" data-tabs id="hotel-tabs">
<li class="tabs-title is-active">
<a href="#panel-hotel">Hotel Info</a>
</li>
<li class="tabs-title">
<a href="#panel-rooms">Rooms</a>
</li>
<li class="tabs-title">
<a href="#panel-promos">Promotions</a>
</li>
</ul>
<div class="tabs-content" data-tabs-content="hotel-tabs">
<div class="tabs-panel is-active" id="panel-hotel">
<!-- Hotel form fields -->
</div>
<div class="tabs-panel" id="panel-rooms">
<!-- Rooms content -->
</div>
<div class="tabs-panel" id="panel-promos">
<!-- Promotions content -->
</div>
</div>
3. Foundation Reveal (Modal Dialogs)¶
Usage: All popup forms
HTML Structure:
<div class="large reveal" id="newcal-dialog" data-reveal>
<!-- Dialog content -->
<button class="close-button" data-close aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
Opening:
Closing:
Programmatic Close:
4. Foundation Datepicker¶
Usage: All date input fields
HTML:
<input id="f_newcalfrom"
data-date-format="dd-mm-yyyy"
type="text"
class="fdatepicker"
placeholder="Stay From Date" />
Initialization:
Getting Date Object:
const fromDate = $('#f_newcalfrom')
.fdatepicker({ format: 'dd-mm-yyyy' })
.data("datepicker")
.getDate();
Setting Date:
5. Notify.js (Toast Notifications)¶
Usage: Success/error messages
Success Notification (at element):
Global Notification:
Error Notification:
Parameters:
- Message text
- Type: "success", "warn", "error"
- Options: {position: "top center"} etc.
Helper Functions¶
Date Formatting¶
// Convert JavaScript Date to "dd-mm-yyyy" string
glb.webDate(dateObj);
// Convert "dd-mm-yyyy" string to display format
glb.displayDate(dateObj);
// Example:
const today = new Date();
const webFormat = glb.webDate(today); // "26-11-2025"
Confirmation Dialog¶
function ask(theQuestion, whenYes, whenNo) {
$('#confirm_text').html(theQuestion);
$('#dlg_btn_ok').one('click', function(event) {
$('#confirm_dialog').foundation('close');
if(whenYes)
whenYes(event);
});
$('#dlg_btn_cancel').one('click', function(event) {
$('#confirm_dialog').foundation('close');
if(whenNo)
whenNo(event);
});
$('#confirm_dialog').foundation('open');
}
// Usage:
ask("Are you sure you want to delete this room?",
() => { deleteRoom(); },
() => { console.log("Cancelled"); }
);
Wait Dialog¶
// Show loading spinner
showWaitDialog();
showWaitDialog('small', '#wait-div-htlcal'); // With custom selector
// Hide loading spinner
hideWaitDialog();
Message Sections¶
// Show inline message
showMessageSection('warning', 'Please fill all required fields', '#div-msg-req', '#var-msg-req');
// Hide inline message
hideMessageSection('#div-msg-req');
Event Handling Patterns¶
Click Events¶
// Method 1: Direct handler assignment
$('#updatehotel_btn').on("click", updateHotel);
// Method 2: Delegated event (for dynamically created elements)
$('a[data-result-id]').on("click", showPriceBreakdown);
// Method 3: One-time event
$('#dlg_btn_ok').one('click', function(event) {
// Handler runs only once
});
Change Events¶
// Dropdown change
$('#f_roomselect').on("change", updateRoomData);
// Checkbox change
$('#f_calmlsupp').on("change", populateSelectedMealPlan);
Form Submission¶
Note: No form submit events used; all actions triggered by button clicks
AJAX Call Pattern¶
All API calls use the glb.tlinq() wrapper:
glb.tlinq('hotel/saveHotel', apiParam).then(
(result) => {
// Success handler
console.log("Success:", result);
$.notify("Update successful", "success");
},
(error) => {
// Error handler
console.error("Error:", error);
$.notify("Error: " + error.errorMessage, "error");
}
);
glb.tlinq() Implementation (in globals.js, not shown): - Builds full API URL - Adds session token if not in params - Makes jQuery AJAX POST request - Returns Promise - Handles common errors
Performance Optimizations¶
1. Map-Based Lookups¶
Instead of array searches:
// Fast: O(1) lookup
roomMap.set(room.roomId, room);
const room = roomMap.get(roomId);
// Slow: O(n) search
rooms.find(r => r.roomId === roomId);
2. Event Delegation¶
For dynamically created search results:
// Attach once to parent
$('a[data-result-id]').on("click", showPriceBreakdown);
// Better than attaching to each link individually
3. Conditional Loading¶
Only load calendar when needed:
4. Batch DOM Updates¶
Build complete HTML string before appending:
// Good: Single append
const table = $('<table></table>');
rows.forEach(row => {
table.append(buildRow(row));
});
$('#container').append(table);
// Bad: Multiple appends to live DOM
rows.forEach(row => {
$('#container').append(buildRow(row));
});
Error Handling¶
API Error Pattern¶
glb.tlinq('hotel/saveHotel', apiParam).then(
(result) => {
// Success
},
(error) => {
// API returned error response
console.error("API Error: " + error.errorCode + " - " + error.errorMessage);
$.notify("Error: " + error.errorMessage, "error");
}
);
Client-Side Validation¶
Example: Validate dates before API call:
const fromDate = $('#f_newcalfrom').fdatepicker().data("datepicker").getDate();
const toDate = $('#f_newcalto').fdatepicker().data("datepicker").getDate();
if(fromDate >= toDate) {
$.notify("End date must be after start date!", "error");
return; // Prevent API call
}
Error Notification Locations¶
-
At Button: For button-specific actions
-
Global: For general operations
-
Console Log: For debugging
Session Management¶
Session Token¶
Stored in browser's sessionStorage:
// Get session token
const session = sessionStorage.getItem("ss_user_uid");
// All API calls include session
glb.tlinq('hotel/listHotels', {"session": session, ...});
Session Expiry Handling¶
Handled by glb.tlinq() wrapper:
- Detects SESSION_ERROR response
- Redirects to login page
- Clears sessionStorage
Browser Compatibility¶
Requirements¶
- JavaScript: ES6 (2015) support required
- Modules
- Arrow functions
- Template literals
- Map/Set objects
-
Promises
-
CSS: Foundation 6.x requirements
- Flexbox
-
CSS Grid (optional)
-
Supported Browsers:
- Chrome 51+
- Firefox 54+
- Safari 10+
- Edge 15+
Not Supported¶
- Internet Explorer (any version)
- Older mobile browsers
Accessibility Considerations¶
ARIA Attributes¶
Foundation components include built-in ARIA support:
<button class="close-button" data-close aria-label="Close modal">
<span aria-hidden="true">×</span>
</button>
Keyboard Navigation¶
- Tab through form fields
- Enter to submit/confirm
- Escape to close dialogs (Foundation default)
- Arrow keys in dropdowns
Screen Reader Support¶
- Form labels properly associated with inputs
- Required fields indicated
- Error messages announced
- Dialog titles announced
Future Frontend Enhancements¶
1. Validation Library Integration¶
Replace manual validation with library like: - jQuery Validation Plugin - Parsley.js - Vuelidate (if migrating to Vue)
2. State Management Framework¶
For complex state, consider: - Redux (with React) - Vuex (with Vue) - MobX
3. Module Bundler¶
Use Webpack or Rollup to: - Bundle modules - Minify code - Tree-shake unused code - Enable hot module replacement
4. TypeScript Migration¶
Add type safety:
interface Hotel {
hotelId: number;
name: string;
area: string;
stars: number;
// ...
}
function selectHotel(hotel: Hotel): void {
selectedHotel = hotel;
showHotelData();
}
5. Component Framework¶
Consider migrating to: - React - Vue.js - Angular
Benefits: - Reactive data binding - Component reusability - Virtual DOM performance - Better testing support
6. Progressive Web App (PWA)¶
Add offline capabilities: - Service worker for caching - IndexedDB for local data - Background sync - Push notifications
Testing Considerations¶
Manual Testing Checklist¶
Hotel Management: - [ ] Create new hotel - [ ] Update existing hotel - [ ] Search hotels by name - [ ] Search hotels by area - [ ] Hotel list accordion navigation
Room Management: - [ ] Add new room to hotel - [ ] Update room details - [ ] Room dropdown selection - [ ] Occupancy validation
Calendar Management: - [ ] Create calendar entries (90 days) - [ ] Weekend vs weekday rates - [ ] Update existing entries - [ ] Copy rates between rooms - [ ] Monthly calendar view - [ ] Hover tooltips on calendar
Search & Booking: - [ ] Search all hotels - [ ] Filter by area/meal plan - [ ] Sort by price - [ ] View price breakdown - [ ] Create booking request - [ ] MLOS validation warning
Meal Plans: - [ ] Configure hotel meal plans - [ ] Meal supplement pricing - [ ] Normal vs special day costs
Automated Testing Opportunities¶
Unit Tests (with Jest or Mocha): - Date formatting functions - Price calculation logic - Data binding helpers
Integration Tests (with Cypress or Playwright): - Full user workflows - API integration - Dialog interactions
End-to-End Tests: - Complete booking flow - Calendar creation flow - Multi-hotel search
Summary¶
The Hotel Management frontend is a sophisticated single-page application built with:
Architecture: - ✅ ES6 module structure - ✅ State management with Maps - ✅ Custom data binding system - ✅ Event-driven interactions
UI Components: - ✅ Foundation accordion for navigation - ✅ Foundation tabs for organization - ✅ Foundation modals for forms - ✅ Custom calendar grid visualization - ✅ Toast notifications for feedback
Key Features: - ✅ Dynamic hotel/room loading - ✅ Complex rate calendar management - ✅ Multi-market pricing support - ✅ Real-time search with price calculation - ✅ Detailed price breakdown display - ✅ Flexible meal plan configuration - ✅ Booking request creation
Performance: - ✅ Map-based lookups for speed - ✅ Batch DOM updates - ✅ Lazy loading of data - ✅ Event delegation
User Experience: - ✅ Clear visual feedback (notifications) - ✅ Loading indicators for async operations - ✅ Confirmation dialogs for destructive actions - ✅ Inline validation - ✅ Responsive layout (Foundation grid) - ✅ Tooltips with detailed information
This implementation provides a production-ready hotel contract management interface suitable for travel agency operations.