// Calendar tab — monthly grid + sidebar + day hover popups 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"]; 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 || "", description: d.description || "", createdBy: d.createdBy || "" }); }); allEvents.sort((a, b) => (a.date + a.time).localeCompare(b.date + b.time)); 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(); }; function render() { const year = viewDate.getFullYear(); const month = viewDate.getMonth(); navEl.textContent = MONTHS[month] + " " + year; // Group events by date for quick lookup const byDate = {}; allEvents.forEach(e => { if (!byDate[e.date]) byDate[e.date] = []; byDate[e.date].push(e); }); // Build grid 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) { // Dot indicators dayEvents.forEach(() => html += ''); // Hover popup html += '

' + dateStr + "

"; dayEvents.forEach(e => { html += '
'; if (e.time) html += '' + e.time + ""; html += esc(e.title); if (e.description) html += '
' + esc(e.description) + "
"; html += "
"; }); html += "
"; } html += "
"; } gridEl.innerHTML = html; // Sidebar: upcoming events 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 ? " at " + e.time : "") + "
" + '
' + esc(e.title) + "
" + (e.description ? '
' + esc(e.description) + "
" : "") + "
" ).join(""); } } function esc(s) { if (!s) return ""; return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); } });