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); } })();