245 lines
6.9 KiB
JavaScript
245 lines
6.9 KiB
JavaScript
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;
|
|
}, []);
|
|
}
|