// public/public.js (for index.html) document.addEventListener('DOMContentLoaded', async () => { const calendarDates = document.getElementById('calendarDates'); const monthYear = document.getElementById('monthYear'); const prevMonthBtn = document.getElementById('prevMonth'); const nextMonthBtn = document.getElementById('nextMonth'); const currentTimeElement = document.getElementById('currentTime'); const timeSlotsContainer = document.getElementById('timeSlots'); // Check if navigation buttons exist before adding event listeners if (!prevMonthBtn || !nextMonthBtn) { console.error('Navigation buttons (prevMonth or nextMonth) not found in the DOM'); return; } let currentDate = new Date(); let currentMonth = currentDate.getMonth(); let currentYear = currentDate.getFullYear(); let selectedDate = null; // Track selected date const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; // Fetch available dates from the API async function fetchAvailability() { try { const response = await fetch('/api/availability'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const availability = await response.json(); return availability; } catch (error) { console.error('Error fetching availability:', error); return {}; } } // Render the calendar with available dates highlighted and automatically select/display the current date async function renderCalendar(month, year) { // Fetch availability first to ensure data is ready const availability = await fetchAvailability(); calendarDates.innerHTML = ''; monthYear.textContent = `${months[month]} ${year}`; const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const today = new Date(); // Start of today (midnight) const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate()); // Create blank cells for days before the first day of the month for (let i = 0; i < firstDay; i++) { const blank = document.createElement('div'); blank.classList.add('date-item', 'p-2', 'rounded-pill', 'text-center', 'text-muted'); calendarDates.appendChild(blank); } // Populate the calendar with days for (let i = 1; i <= daysInMonth; i++) { const day = document.createElement('div'); day.classList.add('date-item', 'p-2', 'rounded-pill', 'text-center'); day.textContent = i; const date = new Date(year, month, i); const dateStr = date.toISOString().split('T')[0]; // Check if date is in the past const isPastDate = date < startOfToday; if (date.toDateString() === today.toDateString()) { day.classList.add('current'); } if (availability[dateStr]) { day.classList.add('available'); } if (isPastDate) { day.classList.add('text-muted'); day.style.cursor = 'not-allowed'; } else { day.addEventListener('click', () => selectDate(date, firstDay)); day.style.cursor = 'pointer'; } calendarDates.appendChild(day); } // Only try to select today if we're in the current month and year if (today.getMonth() === month && today.getFullYear() === year) { await selectDate(today, firstDay); // Use await to ensure selection and display complete } } // Handle date selection async function selectDate(date, firstDay) { // Check if the date is in the past const today = new Date(); const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate()); if (date < startOfToday) { console.warn('Attempted to select a past date'); return; } selectedDate = date; const dateItems = document.querySelectorAll('#calendarDates .date-item'); dateItems.forEach(item => item.classList.remove('selected')); // Ensure only one date is selected // Find the exact date item for the clicked or auto-selected date const selectedDay = Array.from(dateItems).find(item => { const dayText = item.textContent; const itemDate = new Date(currentYear, currentMonth, parseInt(dayText)); return itemDate.toDateString() === date.toDateString(); }); if (selectedDay) selectedDay.classList.add('selected'); await updateTimeSlots(date); // Update time slots based on selected date, ensuring async completion } // Helper function to convert time string to minutes for sorting function timeToMinutes(timeStr) { console.log('Converting time:', timeStr); const match = timeStr.match(/([0-9]+)(?::([0-9]+))?(am|pm)/i); if (!match) { console.warn('No match for time:', timeStr); return 0; } let [_, hours, minutes, period] = match; hours = parseInt(hours); minutes = minutes ? parseInt(minutes) : 0; period = period.toLowerCase(); if (period === 'pm' && hours !== 12) hours += 12; if (period === 'am' && hours === 12) hours = 0; const totalMinutes = hours * 60 + minutes; console.log(`${timeStr} -> ${totalMinutes} minutes`); return totalMinutes; } // Update time slots based on selected date async function updateTimeSlots(date) { const availability = await fetchAvailability(); const dateStr = date.toISOString().split('T')[0]; let times = []; if (availability[dateStr]) { // Ensure availability[dateStr] is a string or array before processing if (typeof availability[dateStr] === 'string') { times = availability[dateStr].split(',').map(t => t.trim()); } else if (Array.isArray(availability[dateStr])) { times = availability[dateStr].map(t => t.trim()); } else { console.warn(`Unexpected data format for date ${dateStr}:`, availability[dateStr]); } } // Clear available time slots or message timeSlotsContainer.innerHTML = ''; // Get current time in minutes for comparison const now = new Date(); const currentMinutes = now.getHours() * 60 + now.getMinutes(); const isToday = date.toDateString() === now.toDateString(); // Filter out past times if it's today if (isToday) { times = times.filter(time => timeToMinutes(time) > currentMinutes); } // Sort remaining times chronologically times.sort((a, b) => timeToMinutes(a) - timeToMinutes(b)); if (times.length === 0) { // Display appropriate message based on context const noAvailabilityMsg = document.createElement('p'); noAvailabilityMsg.classList.add('text-muted', 'text-center', 'fw-bold', 'fs-5', 'py-3'); let message; if (isToday) { if (currentMinutes > timeToMinutes('5:00pm')) { message = 'No more availability today.'; } else { message = 'No available time slots for today.'; } } else { message = 'No availability on selected date.'; } noAvailabilityMsg.textContent = message; timeSlotsContainer.appendChild(noAvailabilityMsg); } else { // Populate time slots dynamically times.forEach(time => { const button = document.createElement('button'); button.classList.add('time-slot', 'btn', 'rounded-pill', 'w-100', 'mb-2'); button.classList.add('btn-outline-primary', 'available'); button.textContent = time; button.addEventListener('click', () => { if (button.classList.contains('available') && selectedDate) { const allTimeSlots = timeSlotsContainer.querySelectorAll('.time-slot'); allTimeSlots.forEach(s => s.classList.remove('selected')); button.classList.add('selected'); } }); timeSlotsContainer.appendChild(button); }); } } // Navigate to previous month prevMonthBtn.addEventListener('click', () => { currentMonth--; if (currentMonth < 0) { currentMonth = 11; currentYear--; } renderCalendar(currentMonth, currentYear); }); // Navigate to next month nextMonthBtn.addEventListener('click', () => { currentMonth++; if (currentMonth > 11) { currentMonth = 0; currentYear++; } renderCalendar(currentMonth, currentYear); }); // Update current time in Eastern Time (US & Canada) function updateCurrentTime() { const options = { timeZone: 'America/New_York', hour: '2-digit', minute: '2-digit', hour12: true }; const time = new Date().toLocaleTimeString('en-US', options); currentTimeElement.textContent = time; } // Update time every second setInterval(updateCurrentTime, 1000); updateCurrentTime(); // Initial call // Initial render (set to current date, automatically selecting and displaying it) await renderCalendar(currentMonth, currentYear); // Use await to ensure the initial render completes });