const state = {
propertyGroupId: null,
rooms: [],
};
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
function formatDt(iso) {
return formatUtcDateTime(iso);
}
function defaultStayRange() {
const start = new Date();
const end = new Date();
end.setDate(end.getDate() + 30);
const fmt = (d) => {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
};
return { start: fmt(start), end: fmt(end) };
}
function renderRooms() {
const sel = document.getElementById('resRoomFilter');
const current = sel.value;
sel.innerHTML = '';
state.rooms.forEach((r) => {
const opt = document.createElement('option');
opt.value = r.id;
opt.textContent = r.display_name;
sel.appendChild(opt);
});
if (current && state.rooms.some((r) => r.id === current)) {
sel.value = current;
}
}
function statusBadge(row) {
if (!row.is_active) {
return '취소';
}
if (row.masked) {
return '마스킹';
}
return '확정';
}
function smsCell(row) {
if (!row.needs_check_in_sms) {
return '—';
}
const checked = row.check_in_sms_sent ? 'checked' : '';
const sentLabel = row.check_in_sms_sent
? `${escapeHtml(formatDt(row.check_in_sms_sent_at))}`
: '';
return `
${sentLabel}`;
}
function formatStayCell(row) {
if (row.is_multi_night && row.stay_end_date && row.stay_end_date !== row.stay_date) {
return `${escapeHtml(row.stay_date)} ~ ${escapeHtml(row.stay_end_date)} ${row.night_count}박`;
}
return escapeHtml(row.stay_date);
}
function renderRows(items, total) {
const body = document.getElementById('resTableBody');
const summary = document.getElementById('resSummary');
if (!items.length) {
body.innerHTML = '
| 예약이 없습니다. |
';
summary.textContent = '0건';
return;
}
summary.textContent = `${total}건 · 표시 ${items.length}건`;
body.innerHTML = items.map((row) => {
const guestCls = row.masked ? ' res-cell-masked' : '';
return `
| ${formatStayCell(row)} |
${escapeHtml(row.display_name || row.room_name || '—')} |
${escapeHtml(row.reservation_no || '—')} |
${escapeHtml(row.guest_name || '—')} |
${escapeHtml(row.guest_phone || '—')} |
${statusBadge(row)} |
${smsCell(row)} |
`;
}).join('');
body.querySelectorAll('.res-sms-toggle').forEach((el) => {
el.addEventListener('change', onSmsToggle);
});
}
async function onSmsToggle(ev) {
const input = ev.target;
const slotId = input.dataset.slotId;
const sent = input.checked;
input.disabled = true;
try {
await API.patch(`/api/v1/reservation-slots/${slotId}/check-in-sms`, { sent });
await loadReservations();
} catch (err) {
input.checked = !sent;
alert(`저장 실패: ${err.message}`);
} finally {
input.disabled = false;
}
}
async function loadReservations() {
if (!state.propertyGroupId) return;
const params = new URLSearchParams({
property_group_id: state.propertyGroupId,
limit: '500',
});
const start = document.getElementById('resStayStart').value;
const end = document.getElementById('resStayEnd').value;
const roomId = document.getElementById('resRoomFilter').value;
const guestName = document.getElementById('resGuestName').value.trim();
const reservationNo = document.getElementById('resReservationNo').value.trim();
if (start) params.set('stay_start_date', start);
if (end) params.set('stay_end_date', end);
if (roomId) params.set('room_mapping_id', roomId);
if (guestName) params.set('guest_name', guestName);
if (reservationNo) params.set('reservation_no', reservationNo);
if (document.getElementById('resIncludeCancelled').checked) {
params.set('include_cancelled', 'true');
}
if (document.getElementById('resSmsPendingOnly').checked) {
params.set('check_in_sms_pending', 'true');
}
const loading = document.getElementById('resLoading');
loading.classList.remove('hidden');
try {
const data = await API.get(`/api/v1/reservation-slots?${params}`);
renderRows(data.items || [], data.total ?? 0);
} catch (err) {
document.getElementById('resTableBody').innerHTML =
`| 로드 실패: ${escapeHtml(err.message)} |
`;
} finally {
loading.classList.add('hidden');
}
}
async function loadRooms() {
if (!state.propertyGroupId) {
state.rooms = [];
renderRooms();
return;
}
state.rooms = await API.get(
`/api/v1/room-mappings?property_group_id=${state.propertyGroupId}&exposed_only=true`,
);
renderRooms();
}
document.getElementById('resRefreshBtn').addEventListener('click', loadReservations);
document.getElementById('resGuestName').addEventListener('keydown', (ev) => {
if (ev.key === 'Enter') loadReservations();
});
document.getElementById('resReservationNo').addEventListener('keydown', (ev) => {
if (ev.key === 'Enter') loadReservations();
});
(async function init() {
if (!requireAuth()) return;
await applyAdminNav();
const range = defaultStayRange();
document.getElementById('resStayStart').value = range.start;
document.getElementById('resStayEnd').value = range.end;
try {
await initCatalogSelectors({
onPropertyChange: async (propertyGroupId) => {
state.propertyGroupId = propertyGroupId;
await loadRooms();
await loadReservations();
},
});
} catch (err) {
console.error(err);
}
})();