// Calendar tab — timezone-aware (auto-converts to your local time) const CAL_FB_CONFIG = { apiKey: "AIzaSyDuSjEGEKx5FnWYnQq8f_owbpYRBRrl5x0", authDomain: "esef-514bf.firebaseapp.com", projectId: "esef-514bf", storageBucket: "esef-514bf.firebasestorage.app", messagingSenderId: "353969651290", appId: "1:353969651290:web:41e3aaf8e54c743702a263" }; const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const MONTHS = ["January","February","March","April","May","June", "July","August","September","October","November","December"]; function fmtTime(utcIso) { if (utcIso) { try { var d = new Date(utcIso); if (!isNaN(d.getTime())) { return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } } catch (e) {} } return ""; } document.addEventListener("DOMContentLoaded", () => { const tab = document.getElementById("calendar"); if (!tab) return; if (!firebase.apps.length) firebase.initializeApp(CAL_FB_CONFIG); const db = firebase.firestore(); let allEvents = []; let viewDate = new Date(); const gridEl = document.getElementById("cal-grid"); const navEl = document.getElementById("cal-nav-title"); const sidebarEl = document.getElementById("cal-sidebar-list"); db.collection("calendar_events").onSnapshot(snap => { allEvents = []; snap.forEach(doc => { const d = doc.data(); allEvents.push({ id: doc.id, title: d.title || "", date: d.date || "", time: d.time || "", utc: d.utc || "", description: d.description || "", _sort: d.utc || (d.date + "T" + (d.time || "00:00")) }); }); allEvents.sort((a, b) => a._sort.localeCompare(b._sort)); render(); }, () => { if (gridEl) gridEl.innerHTML = '

Could not load.

'; }); document.getElementById("cal-prev").onclick = () => { viewDate.setMonth(viewDate.getMonth()-1); render(); }; document.getElementById("cal-next").onclick = () => { viewDate.setMonth(viewDate.getMonth()+1); render(); }; // Auto-refresh every 60s so "Upcoming" stays current as time passes setInterval(render, 60000); function render() { const year = viewDate.getFullYear(); const month = viewDate.getMonth(); navEl.textContent = MONTHS[month] + " " + year; const byDate = {}; allEvents.forEach(e => { if (!byDate[e.date]) byDate[e.date] = []; byDate[e.date].push(e); }); const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const today = new Date(); const todayStr = today.getFullYear() + "-" + String(today.getMonth()+1).padStart(2,"0") + "-" + String(today.getDate()).padStart(2,"0"); let html = ""; WEEKDAYS.forEach(d => html += '
' + d + "
"); for (let i = 0; i < firstDay; i++) { html += '
'; } for (let d = 1; d <= daysInMonth; d++) { const dateStr = year + "-" + String(month+1).padStart(2,"0") + "-" + String(d).padStart(2,"0"); const dayEvents = byDate[dateStr] || []; const isToday = dateStr === todayStr; const hasEvents = dayEvents.length > 0; html += '
'; html += '
' + d + "
"; if (hasEvents) { dayEvents.forEach(() => html += ''); html += '

' + dateStr + "

"; dayEvents.forEach(e => { html += '
'; if (e.time) { html += '' + fmtTime(e.utc) + " "; } html += esc(e.title); if (e.description) html += '
' + esc(e.description) + "
"; html += "
"; }); html += "
"; } html += "
"; } gridEl.innerHTML = html; const upcoming = allEvents.filter(e => e.date >= todayStr); if (upcoming.length === 0) { sidebarEl.innerHTML = '

No upcoming events.
Use !book in Discord.

'; } else { sidebarEl.innerHTML = upcoming.slice(0, 15).map(e => '
' + '
' + e.date + (e.time ? " " + fmtTime(e.utc) : "") + "
" + '
' + esc(e.title) + "
" + (e.description ? '
' + esc(e.description) + "
" : "") + "
" ).join(""); } } function esc(s) { if (!s) return ""; return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); } });