function parseDate(value) { const parts = value.split('-').map(Number); return new Date(parts[0], parts[1] - 1, parts[2]); } function formatDate(date) { const year = date.getFullYear(); const month = `${date.getMonth() + 1}`.padStart(2, '0'); const day = `${date.getDate()}`.padStart(2, '0'); return `${year}-${month}-${day}`; } function addDays(date, count) { const next = new Date(date); next.setDate(next.getDate() + count); return next; } function addMonths(date, count) { return new Date(date.getFullYear(), date.getMonth() + count, 1); } function startOfWeek(date) { return addDays(date, -date.getDay()); } function endOfWeek(date) { return addDays(date, 6 - date.getDay()); } function endOfMonth(date) { return new Date(date.getFullYear(), date.getMonth() + 1, 0); } const monthLabels = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']; export function buildCalendarMonths({ today, monthCount, weekdayLabels }) { const todayDate = parseDate(today); const months = []; for (let monthIndex = 0; monthIndex < monthCount; monthIndex++) { const monthDate = addMonths(todayDate, monthIndex); const monthStart = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1); const start = startOfWeek(monthStart); const end = endOfWeek(endOfMonth(monthStart)); const weeks = []; let cursor = start; let weekIndex = 0; while (cursor <= end) { const days = weekdayLabels.map((item, dayIndex) => { const date = addDays(cursor, dayIndex); const dateText = formatDate(date); const isLeadingPastDate = monthIndex === 0 && date < todayDate; const isOutsideMonth = date.getMonth() !== monthStart.getMonth() && !isLeadingPastDate; return { date: dateText, dayNo: isOutsideMonth ? '' : `${date.getDate()}`.padStart(2, '0'), isToday: !isOutsideMonth && dateText === today, isPast: date < todayDate, isOutsideMonth, }; }); weeks.push({ weekKey: formatDate(cursor), days, }); cursor = addDays(cursor, 7); weekIndex++; } months.push({ monthKey: formatDate(monthStart).slice(0, 7), monthLabel: monthLabels[monthStart.getMonth()], weeks, }); } return months; } export function buildCalendarDateRange({ today, startDate, endDate, weekdayLabels }) { const todayDate = parseDate(today); const rangeStart = parseDate(startDate); const rangeEnd = parseDate(endDate); const months = []; let monthDate = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), 1); const lastMonthDate = new Date(rangeEnd.getFullYear(), rangeEnd.getMonth(), 1); while (monthDate <= lastMonthDate) { const monthStart = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1); const visibleStart = rangeStart > monthStart ? rangeStart : monthStart; const monthEnd = endOfMonth(monthStart); const visibleEnd = rangeEnd < monthEnd ? rangeEnd : monthEnd; const start = startOfWeek(visibleStart); const end = endOfWeek(visibleEnd); const weeks = []; let cursor = start; while (cursor <= end) { const days = weekdayLabels.map((item, dayIndex) => { const date = addDays(cursor, dayIndex); const dateText = formatDate(date); const isOutsideMonth = date < rangeStart || date > rangeEnd || date.getMonth() !== monthStart.getMonth(); return { date: dateText, dayNo: isOutsideMonth ? '' : `${date.getDate()}`.padStart(2, '0'), isToday: !isOutsideMonth && dateText === today, isPast: date < todayDate, isOutsideMonth, }; }); weeks.push({ weekKey: formatDate(cursor), days, }); cursor = addDays(cursor, 7); } months.push({ monthKey: formatDate(monthStart).slice(0, 7), monthLabel: monthLabels[monthStart.getMonth()], weeks, }); monthDate = addMonths(monthDate, 1); } return months; } export function buildContinuousCalendarWeeks(months) { const weeks = []; months.forEach(month => { month.weeks.forEach((week, weekIndex) => { const days = week.days.map(day => ({ ...day })); const startsMonth = days.some(day => day.dayNo === '01'); weeks.push({ ...week, weekId: `${month.monthKey}-${week.weekKey}`, days, monthLabel: weekIndex === 0 || startsMonth ? month.monthLabel : '', }); }); }); return weeks; } export function getTodayWeekId(months) { for (const month of months) { for (const week of month.weeks) { if (week.days.some(day => day.isToday && !day.isOutsideMonth)) { return `week-${month.monthKey}-${week.weekKey}`; } } } return ''; } export function getAvailabilityStatusClass(item) { if (item.count > 0) return 'available'; return 'zero'; } export function getCriteriaStatusClass(item) { if (!item.statusText) return ''; return item.qualified ? 'qualified' : 'unqualified'; } function getRecordValue(record, lowerKey, upperKey) { return record[lowerKey] !== undefined ? record[lowerKey] : record[upperKey]; } function normalizeRecordDate(value) { if (!value) return ''; return String(value).slice(0, 10); } function formatCriteria(value) { if (value === undefined || value === null || value === '') return '-'; const numberValue = Number(value); if (Number.isNaN(numberValue)) return String(value); return numberValue.toLocaleString('en-US'); } export function mapRoomCalendarQualificationRecords(records = []) { return records.reduce((map, record) => { const date = normalizeRecordDate(getRecordValue(record, 'date', 'Date')); const roomType = getRecordValue(record, 'roomType', 'RoomType'); if (!date || !roomType) return map; const flag = Number(getRecordValue(record, 'bookingDateQualificationFlag', 'BookingDateQualificationFlag')); map[`${date}_${roomType}`] = { criteria: formatCriteria(getRecordValue(record, 'roomCriteria', 'RoomCriteria')), count: Number(getRecordValue(record, 'roomCount', 'RoomCount') || 0), leader: getRecordValue(record, 'propertyLeader', 'PropertyLeader') || getRecordValue(record, 'leader', 'Leader') || '', qualified: flag === 1, statusText: flag === 1 ? 'Qualified' : 'Unqualified', }; return map; }, {}); } export function getLeaderForRoomType(roomType) { const leaderMap = { GR: 'Samuel H.', X1: 'Hope C.', X2: 'Clara N.', R1: 'Hugo L.', R2: 'Selina T.', MO_MOGR: 'Hugo L.', MO_SUIT: 'Hugo L.', MO: 'Hugo L.', ASC: 'Clara N.', LMG: 'Selina T.', LAR: 'Kevin L.', YOBT: 'Momo C.', HBV: 'Hope C.', YOHO: 'Samuel H.', }; return leaderMap[roomType.code] || 'Kevin L.'; } export function getOtherPropertyRoomTypes(roomTypes) { const displayNameMap = { ASC: 'Ascott', }; return roomTypes.reduce((list, roomType) => { if (roomType.code === 'MO') { list.push( { ...roomType, code: 'MO_MOGR', label: 'MO', displayName: 'MOGR', parentCode: 'MO' }, { ...roomType, code: 'MO_SUIT', label: 'MO', displayName: 'MO Suit', parentCode: 'MO' }, ); return list; } list.push({ ...roomType, displayName: roomType.displayName || displayNameMap[roomType.code], parentCode: roomType.parentCode || roomType.code, }); return list; }, []); }