Files
2026-09-16 10:43:59 +08:00

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;
}, []);
}