/** * 밍글 스튜디오 - 예약 현황 캘린더 * Google Apps Script 프록시를 통해 캘린더 데이터를 가져와 표시 */ (function() { 'use strict'; var APPS_SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbwoaoSmEskbJod4gR5NjLnpWSkhIDUYaGBYk0y1Q865TzaJ4gctSrTOWXxRnVa7J9AASA/exec'; var CACHE_TTL = 30 * 60 * 1000; // 30분 var currentYear, currentMonth; var calTitle = document.getElementById('calTitle'); var calBody = document.getElementById('calBody'); var prevBtn = document.getElementById('prevMonth'); var nextBtn = document.getElementById('nextMonth'); function init() { var now = new Date(); currentYear = now.getFullYear(); currentMonth = now.getMonth() + 1; prevBtn.addEventListener('click', function() { changeMonth(-1); }); nextBtn.addEventListener('click', function() { changeMonth(1); }); renderCalendar(); } function changeMonth(delta) { currentMonth += delta; if (currentMonth > 12) { currentMonth = 1; currentYear++; } else if (currentMonth < 1) { currentMonth = 12; currentYear--; } renderCalendar(); } function renderCalendar() { updateTitle(); var cached = getCache(currentYear, currentMonth); if (cached !== null) { buildGrid(cached); } else { // 즉시 빈 그리드 렌더링 (체감 속도 향상) buildGrid([]); calBody.classList.add('loading'); fetchBookedDates(currentYear, currentMonth, function(dates) { setCache(currentYear, currentMonth, dates); buildGrid(dates); calBody.classList.remove('loading'); prefetchAdjacent(); }); } } function prefetchAdjacent() { var nextM = currentMonth + 1, nextY = currentYear; if (nextM > 12) { nextM = 1; nextY++; } if (getCache(nextY, nextM) === null) { fetchBookedDates(nextY, nextM, function(dates) { setCache(nextY, nextM, dates); }); } } // --- localStorage 캐싱 --- function cacheKey(y, m) { return 'mingle_cal_' + y + '_' + m; } function getCache(y, m) { try { var raw = localStorage.getItem(cacheKey(y, m)); if (!raw) return null; var obj = JSON.parse(raw); if (Date.now() - obj.ts > CACHE_TTL) { localStorage.removeItem(cacheKey(y, m)); return null; } return obj.dates; } catch (e) { return null; } } function setCache(y, m, dates) { try { localStorage.setItem(cacheKey(y, m), JSON.stringify({ dates: dates, ts: Date.now() })); } catch (e) { /* quota exceeded 등 무시 */ } } // --- 제목 업데이트 --- function updateTitle() { var lang = (window.i18n && window.i18n.currentLang) || 'ko'; var monthNames = { ko: ['1월','2월','3월','4월','5월','6월','7월','8월','9월','10월','11월','12월'], en: ['January','February','March','April','May','June','July','August','September','October','November','December'], ja: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], zh: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'] }; var names = monthNames[lang] || monthNames.ko; if (lang === 'en') { calTitle.textContent = names[currentMonth - 1] + ' ' + currentYear; } else if (lang === 'ja' || lang === 'zh') { calTitle.textContent = currentYear + '年 ' + names[currentMonth - 1]; } else { calTitle.textContent = currentYear + '년 ' + names[currentMonth - 1]; } } // --- API 호출 --- function fetchBookedDates(year, month, callback) { if (!APPS_SCRIPT_URL) { callback([]); return; } var url = APPS_SCRIPT_URL + '?year=' + year + '&month=' + month; fetch(url) .then(function(res) { return res.json(); }) .then(function(data) { callback(data.bookedDates || []); }) .catch(function() { callback([]); }); } // --- 그리드 렌더링 --- function buildGrid(bookedDates) { calBody.innerHTML = ''; var firstDay = new Date(currentYear, currentMonth - 1, 1).getDay(); var daysInMonth = new Date(currentYear, currentMonth, 0).getDate(); var today = new Date(); var todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0'); var bookedSet = {}; for (var b = 0; b < bookedDates.length; b++) { bookedSet[bookedDates[b]] = true; } var fragment = document.createDocumentFragment(); for (var e = 0; e < firstDay; e++) { var emptyCell = document.createElement('div'); emptyCell.className = 'cal-cell empty'; fragment.appendChild(emptyCell); } for (var d = 1; d <= daysInMonth; d++) { var dateStr = currentYear + '-' + String(currentMonth).padStart(2, '0') + '-' + String(d).padStart(2, '0'); var cell = document.createElement('div'); cell.className = 'cal-cell'; var dayNum = document.createElement('span'); dayNum.className = 'day-num'; dayNum.textContent = d; var cellDate = new Date(currentYear, currentMonth - 1, d); var isPast = cellDate < new Date(today.getFullYear(), today.getMonth(), today.getDate()); if (dateStr === todayStr) { cell.classList.add('today'); } else if (isPast) { cell.classList.add('past'); } else if (bookedSet[dateStr]) { cell.classList.add('booked'); } else { cell.classList.add('available'); } cell.appendChild(dayNum); fragment.appendChild(cell); } calBody.appendChild(fragment); } document.addEventListener('langChanged', function() { updateTitle(); }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();