245 lines
9.4 KiB
JavaScript
245 lines
9.4 KiB
JavaScript
// 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
|
|
}); |