// Constants for API endpoints and configuration const API_CONFIG = { BASE_URL: ‘https://wocstgold.wpengine.com/wp-json’, TICKETS_ENDPOINT: ‘/tribe/tickets/v1/tickets/’, EVENTS_ENDPOINT: ‘/tribe/events/v1/events’, TIMEZONE: ‘America/Los_Angeles’ }; // DOM Element References const DOM_ELEMENTS = { daysContainer: document.getElementById(‘daysContainer’), weekRange: document.getElementById(‘weekRange’), eventsList: document.getElementById(‘eventsList’), prevWeek: document.getElementById(‘prevWeek’), nextWeek: document.getElementById(‘nextWeek’) }; // Application State class CalendarState { constructor() { this.currentDate = new Date(); this.events = {}; this.debounceTimer = null; } // Utility method to get local timezone date getLocalDate(date = new Date()) { return new Date(date.toLocaleString(‘en-US’, { timeZone: API_CONFIG.TIMEZONE })); } } // API Service for fetching events and tickets class EventAPIService { // Fetch all tickets from the API static async fetchAllTickets() { const baseUrl = ${API_CONFIG.BASE_URL}${API_CONFIG.TICKETS_ENDPOINT}; let allTickets = []; let currentPage = 1; let totalPages = 1; try { do { const response = await fetch(${baseUrl}?page=${currentPage}); const data = await response.json(); allTickets = allTickets.concat(data.tickets); if (currentPage === 1) { totalPages = data.total_pages; } currentPage++; } while (currentPage <= totalPages); return allTickets; } catch (error) { console.error(‘Error fetching tickets:’, error); return []; } } // Fetch events for a specific week static async fetchEventsForWeek(startDate, endDate) { const eventsUrl = ${API_CONFIG.BASE_URL}${API_CONFIG.EVENTS_ENDPOINT}?start_date=${startDate}&end_date=${endDate}; try { const [eventsResponse, allTickets] = await Promise.all([ fetch(eventsUrl), this.fetchAllTickets() ]); const eventsData = await eventsResponse.json(); return this._processEventsWithTickets(eventsData.events, allTickets); } catch (error) { console.error(‘Error fetching events:’, error); return {}; } } // Process events and map ticket information static _processEventsWithTickets(events, tickets) { const processedEvents = {}; const ticketDetails = this._createTicketDetailsMap(tickets); events.forEach((event) => { const eventDate = new Date(event.start_date).toISOString().split(‘T’)[0]; const eventTime = new Date(event.start_date).toLocaleTimeString(‘en-US’, { hour: ‘2-digit’, minute: ‘2-digit’ }); const ticketInfo = ticketDetails[event.id] || {}; if (!processedEvents[eventDate]) { processedEvents[eventDate] = []; } processedEvents[eventDate].push({ title: event.title, time: eventTime, description: event.description || ‘No description provided’, url: event.url, location: event.venue?.venue || ‘N/A’, organizer: event.organizer[0]?.organizer || ‘N/A’, fullDate: event.start_date, ticketName: ticketInfo.title || ‘No ticket information’, ticketSaleDate: ticketInfo.saleDate || ‘Ticket sale date not specified’, ticketCost: ticketInfo.cost || ‘Cost not available’ }); }); return processedEvents; } // Create a map of ticket details for efficient lookup static _createTicketDetailsMap(tickets) { return tickets.reduce((map, ticket) => { if (ticket.available_from) { map[ticket.post_id] = { saleDate: ticket.available_from, title: ticket.title, cost: ticket.cost }; } return map; }, {}); } } // Calendar Renderer class CalendarRenderer { constructor(state) { this.state = state; } // Update the week display and render days async updateWeek() { this._clearContainers(); const { startOfWeek, endOfWeek, today } = this._calculateWeekBoundaries(); this._updateWeekRangeDisplay(startOfWeek, endOfWeek); const startDate = startOfWeek.toISOString().split(‘T’)[0]; const endDate = endOfWeek.toISOString().split(‘T’)[0]; this.state.events = await EventAPIService.fetchEventsForWeek(startDate, endDate); this._renderDays(startOfWeek, today); this._displayEvents(); } // Calculate week boundaries in local timezone _calculateWeekBoundaries() { const localToday = this.state.getLocalDate(); console.log(‘Local Today:’, localToday); // Calculate start of the week (Sunday) based on the local today’s date const startOfWeek = new Date(localToday); startOfWeek.setDate(localToday.getDate() – localToday.getDay()); startOfWeek.setHours(0, 0, 0, 0); // Calculate end of the week (Saturday) const endOfWeek = new Date(startOfWeek); endOfWeek.setDate(startOfWeek.getDate() + 6); endOfWeek.setHours(23, 59, 59, 999); console.log(‘Start of Week:’, startOfWeek); console.log(‘End of Week:’, endOfWeek); return { startOfWeek, endOfWeek, today: localToday }; } // Get the start of the week (Sunday) _getWeekStart(date) { const start = new Date(date); start.setDate(start.getDate() – start.getDay()); start.setHours(0, 0, 0, 0); return start; } // Get the end of the week (Saturday) _getWeekEnd(date) { const end = new Date(date); end.setDate(end.getDate() + (6 – end.getDay())); end.setHours(23, 59, 59, 999); return end; } // Update week range display _updateWeekRangeDisplay(startOfWeek, endOfWeek) { DOM_ELEMENTS.weekRange.textContent = ${startOfWeek.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })} – ${endOfWeek.toLocaleDateString('en-US', { month: 'long', day: 'numeric' })}; } // Render days of the week _renderDays(startOfWeek, today) { DOM_ELEMENTS.daysContainer.innerHTML = ”; for (let i = 0; i < 7; i++) { const dayDate = new Date(startOfWeek); dayDate.setDate(startOfWeek.getDate() + i); const dayDiv = this._createDayElement(dayDate, today); DOM_ELEMENTS.daysContainer.appendChild(dayDiv); } } // Create day element with appropriate styling and event listeners _createDayElement(dayDate, today) { const dayDiv = document.createElement(‘div’); dayDiv.className = ‘day-item’; dayDiv.textContent = dayDate.getDate(); if (dayDate.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0)) { this._stylePastDate(dayDiv); } else { this._addDayClickHandler(dayDiv, dayDate); } if (dayDate.toDateString() === today.toDateString()) { dayDiv.classList.add(‘current’); } return dayDiv; } // Style past dates _stylePastDate(dayDiv) { dayDiv.classList.add(‘disabled’); dayDiv.style.color = ‘#cccccc’; dayDiv.style.cursor = ‘not-allowed’; dayDiv.style.pointerEvents = ‘none’; } // Add click handler for future dates _addDayClickHandler(dayDiv, dayDate) { dayDiv.addEventListener(‘click’, () => { document.querySelectorAll(‘.day-item’).forEach(item => item.classList.remove(‘current’)); dayDiv.classList.add(‘current’); this._displayEvents(dayDate); }); } // Display events for the week or a specific date _displayEvents(selectedDate = null) { DOM_ELEMENTS.eventsList.innerHTML = ”; const { startOfWeek, endOfWeek } = this._calculateWeekBoundaries(); if (!selectedDate) { this._displayAllWeekEvents(startOfWeek, endOfWeek); } else { this._displaySingleDateEvents(selectedDate); } } // Display all events for the week _displayAllWeekEvents(weekStart, weekEnd) { let eventsDisplayed = false; for (const date in this.state.events) { const eventDate = new Date(date); if (eventDate >= weekStart && eventDate <= weekEnd) { this._createEventSection(date, this.state.events[date]); eventsDisplayed = true; } } if (!eventsDisplayed) { DOM_ELEMENTS.eventsList.innerHTML = ‘
No events scheduled for this week.
‘; } } // Display events for a single date _displaySingleDateEvents(selectedDate) { const isoDate = selectedDate.toISOString().split(‘T’)[0]; if (this.state.events[isoDate]) { this._createEventSection(isoDate, this.state.events[isoDate]); } else { DOM_ELEMENTS.eventsList.innerHTML = `
No events for ${selectedDate.toLocaleDateString()}.
; } } // Create event section for a specific date _createEventSection(date, eventsForDate) { const dateSection = document.createElement('div'); dateSection.className = 'event-date-section'; const eventStartDate = new Date(eventsForDate[0].fullDate); const formattedDate = eventStartDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); dateSection.innerHTML =
${formattedDate}
; eventsForDate.forEach(event => { const eventDiv = this._createEventElement(event); dateSection.appendChild(eventDiv); }); DOM_ELEMENTS.eventsList.appendChild(dateSection); } // Create individual event element _createEventElement(event) { const eventDiv = document.createElement('div'); eventDiv.className = 'event-item'; eventDiv.innerHTML =
${event.title}
Time: ${event.time}
Description: ${this._truncateText(event.description, 50)}
Instructor: ${event.organizer}
Location: ${event.location}
Ticket Name: ${event.ticketName}
Ticket Sale Date: ${event.ticketSaleDate}
Ticket Cost: ${event.ticketCost}
View Details `; return eventDiv; } // Truncate text utility method _truncateText(text, maxLength) { return text.length > maxLength ? text.substring(0, maxLength) + ‘…’ : text; } // Clear containers before rendering _clearContainers() { DOM_ELEMENTS.daysContainer.innerHTML = ”; DOM_ELEMENTS.eventsList.innerHTML = ”; } } // Utility function for debouncing function debounce(func, delay) { return (…args) => { clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(() => func(…args), delay); }; } // Main initialization function async function initializeCalendar() { const calendarState = new CalendarState(); const calendarRenderer = new CalendarRenderer(calendarState); // Week navigation event listeners DOM_ELEMENTS.prevWeek.addEventListener(‘click’, debounce(() => { calendarState.currentDate.setDate(calendarState.currentDate.getDate() – 7); calendarRenderer.updateWeek(); }, 300)); DOM_ELEMENTS.nextWeek.addEventListener(‘click’, debounce(() => { calendarState.currentDate.setDate(calendarState.currentDate.getDate() + 7); calendarRenderer.updateWeek(); }, 300)); // Initial week update await calendarRenderer.updateWeek(); } // Initialize the calendar when the page loads document.addEventListener(‘DOMContentLoaded’, initializeCalendar);