2025-02-21 20:20:12 +00:00
// public/admin.js (for admin.html)
// Global variables
let currentAdminDate = new Date ( ) ;
let currentAdminMonth = currentAdminDate . getMonth ( ) ;
let currentAdminYear = currentAdminDate . getFullYear ( ) ;
let selectedAdminDate = null ;
const months = [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ] ;
// Helper function to convert time string to minutes since midnight
function timeToMinutes ( timeStr ) {
const match = timeStr . match ( /([0-9]+)(?::([0-9]+))?(am|pm)/i ) ;
if ( ! match ) return 0 ;
let hours = parseInt ( match [ 1 ] ) ;
const minutes = parseInt ( match [ 2 ] || '0' ) ;
const meridian = match [ 3 ] . toLowerCase ( ) ;
if ( meridian === 'pm' && hours !== 12 ) {
hours += 12 ;
} else if ( meridian === 'am' && hours === 12 ) {
hours = 0 ;
}
return hours * 60 + minutes ;
}
// Helper function to ensure consistent time format
function formatTimeSlot ( hour ) {
const meridian = hour >= 12 ? 'pm' : 'am' ;
const displayHour = hour > 12 ? hour - 12 : ( hour === 0 ? 12 : hour ) ;
return ` ${ displayHour } :00 ${ meridian } ` ;
}
// Render the admin calendar with available dates highlighted
async function renderAdminCalendar ( month , year ) {
const adminCalendarDates = document . getElementById ( 'adminCalendarDates' ) ;
const adminMonthYear = document . getElementById ( 'adminMonthYear' ) ;
if ( ! adminCalendarDates || ! adminMonthYear ) {
console . error ( 'Required calendar elements not found' ) ;
return ;
}
// Clear existing calendar
adminCalendarDates . innerHTML = '' ;
adminMonthYear . textContent = ` ${ months [ month ] } ${ year } ` ;
// Get current UID and availability
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( ! selectedUid ) {
// If no UID selected, just show empty calendar
return ;
}
const firstDay = new Date ( year , month , 1 ) . getDay ( ) ;
const daysInMonth = new Date ( year , month + 1 , 0 ) . getDate ( ) ;
const today = new Date ( ) ;
2025-02-25 05:27:29 +00:00
const currentMinutes = today . getHours ( ) * 60 + today . getMinutes ( ) ;
2025-02-21 20:20:12 +00:00
const availability = await fetchAvailability ( ) ;
// 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' ) ;
adminCalendarDates . appendChild ( blank ) ;
}
// Clear any existing selected date
const existingSelected = adminCalendarDates . querySelector ( '.selected' ) ;
if ( existingSelected ) {
existingSelected . classList . remove ( 'selected' ) ;
}
// Populate the calendar with days
let todayElement = null ;
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 ;
2025-02-25 05:27:29 +00:00
const isToday = date . toDateString ( ) === today . toDateString ( ) ;
2025-02-21 20:20:12 +00:00
2025-02-25 05:27:29 +00:00
if ( isToday ) {
2025-02-21 20:20:12 +00:00
day . classList . add ( 'current' ) ;
todayElement = day ;
}
2025-02-25 05:27:29 +00:00
// Check if the date has available time slots
let hasAvailableTimes = false ;
2025-02-21 20:20:12 +00:00
if ( availability [ dateStr ] ) {
2025-02-25 05:27:29 +00:00
let times = [ ] ;
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 ( ) ) ;
}
// For today, filter out past times
if ( isToday ) {
times = times . filter ( time => timeToMinutes ( time ) > currentMinutes ) ;
}
// Only mark as available if there are valid times
hasAvailableTimes = times . length > 0 ;
}
// Only add 'available' class if the date is not in the past AND has available times
if ( hasAvailableTimes && ! isPastDate ) {
2025-02-21 20:20:12 +00:00
day . classList . add ( 'available' ) ;
}
2025-02-25 05:27:29 +00:00
2025-02-21 20:20:12 +00:00
if ( isPastDate ) {
day . classList . add ( 'disabled' ) ;
} else {
day . addEventListener ( 'click' , ( ) => selectAdminDate ( date , firstDay ) ) ;
}
adminCalendarDates . appendChild ( day ) ;
}
// If a date was previously selected, re-select it
if ( selectedAdminDate &&
selectedAdminDate . getMonth ( ) === month &&
selectedAdminDate . getFullYear ( ) === year ) {
selectAdminDate ( selectedAdminDate , firstDay ) ;
}
}
// Handle admin date selection
async function selectAdminDate ( date , firstDay ) {
2025-02-25 05:27:29 +00:00
// 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 ;
}
2025-02-21 20:20:12 +00:00
2025-02-25 05:27:29 +00:00
// Update selected date and time slots
selectedAdminDate = date ;
await updateTimeSlots ( date ) ;
const dateItems = document . querySelectorAll ( '#adminCalendarDates .date-item' ) ;
dateItems . forEach ( item => item . classList . remove ( 'selected' ) ) ; // Ensure only one date is selected
// Find the exact date item for the clicked date
const selectedDay = Array . from ( dateItems ) . find ( item => {
2025-02-21 20:20:12 +00:00
const dayText = item . textContent ;
const itemDate = new Date ( currentAdminYear , currentAdminMonth , parseInt ( dayText ) ) ;
return itemDate . toDateString ( ) === date . toDateString ( ) ;
} ) ;
if ( selectedDay ) selectedDay . classList . add ( 'selected' ) ;
2025-02-25 05:27:29 +00:00
// Remove the selected date display text
2025-02-21 20:20:12 +00:00
const selectedDateDisplay = document . getElementById ( 'selectedDateDisplay' ) ;
if ( selectedDateDisplay ) {
2025-02-25 05:27:29 +00:00
selectedDateDisplay . textContent = '' ;
2025-02-21 20:20:12 +00:00
}
}
// Update time slots based on selected date
async function updateTimeSlots ( date ) {
const availability = await fetchAvailability ( ) ;
const dateStr = date . toISOString ( ) . split ( 'T' ) [ 0 ] ;
let availableTimes = [ ] ;
// Handle case where availability might be undefined or null
if ( availability && availability [ dateStr ] ) {
if ( typeof availability [ dateStr ] === 'string' ) {
availableTimes = availability [ dateStr ] . split ( ',' ) . map ( t => t . trim ( ) ) ;
} else if ( Array . isArray ( availability [ dateStr ] ) ) {
2025-02-25 05:27:29 +00:00
availableTimes = [ ... availability [ dateStr ] ] ;
2025-02-21 20:20:12 +00:00
}
}
// Sort available times chronologically
availableTimes . sort ( ( a , b ) => timeToMinutes ( a ) - timeToMinutes ( b ) ) ;
const timeSlots = document . getElementById ( 'timeSlots' ) ;
if ( ! timeSlots ) return ;
2025-02-25 05:27:29 +00:00
// Clear existing time slots
2025-02-21 20:20:12 +00:00
timeSlots . innerHTML = '' ;
2025-02-25 05:27:29 +00:00
// Create time button directly without the container and heading
const timeButton = document . createElement ( 'button' ) ;
timeButton . type = 'button' ;
timeButton . id = 'customTimeInput' ;
timeButton . classList . add ( 'btn' , 'btn-success' , 'btn-lg' , 'w-100' , 'mb-4' ) ;
timeButton . style . backgroundColor = '#2e8b57' ; // Money green color
timeButton . style . borderColor = '#2e8b57' ;
timeButton . style . fontSize = '1.25rem' ;
timeButton . style . padding = '0.75rem 1.25rem' ;
timeButton . innerHTML = '<i class="bi bi-clock"></i> Add Time Slot' ;
// Add button directly to time slots
timeSlots . appendChild ( timeButton ) ;
// Initialize visual time picker
initializeVisualTimePicker ( dateStr , availableTimes ) ;
// Add divider
const divider = document . createElement ( 'hr' ) ;
timeSlots . appendChild ( divider ) ;
// Display existing time slots
if ( availableTimes . length === 0 ) {
const noTimesMessage = document . createElement ( 'p' ) ;
noTimesMessage . classList . add ( 'text-muted' , 'text-center' ) ;
noTimesMessage . textContent = 'No time slots available for this date.' ;
timeSlots . appendChild ( noTimesMessage ) ;
} else {
// Create container for time slot list
const timeSlotsContainer = document . createElement ( 'div' ) ;
timeSlotsContainer . classList . add ( 'd-flex' , 'flex-column' , 'gap-2' , 'mt-3' ) ;
// Display all slots in chronological order
availableTimes . forEach ( time => {
// Create a container div for the time slot
const timeSlotContainer = document . createElement ( 'div' ) ;
timeSlotContainer . classList . add ( 'position-relative' , 'w-100' , 'd-flex' , 'align-items-center' , 'mb-2' ) ;
// Create the time slot pill
const timeSlotItem = document . createElement ( 'div' ) ;
timeSlotItem . classList . add ( 'btn' , 'btn-light' , 'rounded-pill' , 'px-4' , 'py-3' , 'flex-grow-1' ) ;
timeSlotItem . style . backgroundColor = '#e9f2ff' ;
timeSlotItem . style . color = '#0d6efd' ;
timeSlotItem . style . border = '1px solid #0d6efd' ;
timeSlotItem . style . textAlign = 'center' ;
timeSlotItem . style . fontSize = '1.1rem' ;
timeSlotItem . style . fontWeight = '500' ;
timeSlotItem . style . transition = 'all 0.2s ease' ;
// Add time text directly to the pill
timeSlotItem . textContent = time ;
// Add delete button/icon as a separate element
const deleteIcon = document . createElement ( 'button' ) ;
deleteIcon . innerHTML = '<i class="bi bi-trash"></i>' ;
deleteIcon . classList . add ( 'btn' , 'btn-sm' , 'text-danger' , 'ms-3' ) ;
deleteIcon . style . backgroundColor = 'transparent' ;
deleteIcon . style . border = 'none' ;
deleteIcon . style . fontSize = '1.2rem' ;
deleteIcon . style . padding = '8px' ;
deleteIcon . style . cursor = 'pointer' ;
deleteIcon . style . transition = 'all 0.2s ease' ;
deleteIcon . style . opacity = '0.8' ;
deleteIcon . style . borderRadius = '50%' ;
deleteIcon . style . display = 'flex' ;
deleteIcon . style . alignItems = 'center' ;
deleteIcon . style . justifyContent = 'center' ;
// Add hover effects
timeSlotItem . addEventListener ( 'mouseenter' , ( ) => {
timeSlotItem . style . backgroundColor = '#0d6efd' ;
timeSlotItem . style . color = 'white' ;
timeSlotItem . style . transform = 'translateY(-2px)' ;
timeSlotItem . style . boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)' ;
} ) ;
timeSlotItem . addEventListener ( 'mouseleave' , ( ) => {
timeSlotItem . style . backgroundColor = '#e9f2ff' ;
timeSlotItem . style . color = '#0d6efd' ;
timeSlotItem . style . transform = 'translateY(0)' ;
timeSlotItem . style . boxShadow = 'none' ;
} ) ;
// Add hover effects for delete icon
deleteIcon . addEventListener ( 'mouseenter' , ( ) => {
deleteIcon . style . opacity = '1' ;
deleteIcon . style . transform = 'scale(1.2)' ;
deleteIcon . style . backgroundColor = 'rgba(220, 53, 69, 0.1)' ;
} ) ;
deleteIcon . addEventListener ( 'mouseleave' , ( ) => {
deleteIcon . style . opacity = '0.8' ;
deleteIcon . style . transform = 'scale(1)' ;
deleteIcon . style . backgroundColor = 'transparent' ;
} ) ;
deleteIcon . addEventListener ( 'click' , ( e ) => {
e . stopPropagation ( ) ;
2025-02-21 20:20:12 +00:00
removeTime ( dateStr , time ) ;
} ) ;
2025-02-25 05:27:29 +00:00
timeSlotContainer . appendChild ( timeSlotItem ) ;
timeSlotContainer . appendChild ( deleteIcon ) ;
timeSlotsContainer . appendChild ( timeSlotContainer ) ;
} ) ;
timeSlots . appendChild ( timeSlotsContainer ) ;
}
}
// Initialize visual time picker function
function initializeVisualTimePicker ( dateStr , availableTimes ) {
const timeButton = document . getElementById ( 'customTimeInput' ) ;
if ( ! timeButton ) return ;
// Remove any existing time picker
const existingTimePicker = document . getElementById ( 'visualTimePicker' ) ;
if ( existingTimePicker ) {
existingTimePicker . remove ( ) ;
}
// Create the visual time picker container
const timePicker = document . createElement ( 'div' ) ;
timePicker . id = 'visualTimePicker' ;
timePicker . classList . add ( 'card' , 'shadow' ) ;
timePicker . style . display = 'none' ;
timePicker . style . position = 'fixed' ;
timePicker . style . zIndex = '1050' ;
timePicker . style . width = '300px' ;
timePicker . style . top = '50%' ;
timePicker . style . left = '50%' ;
timePicker . style . transform = 'translate(-50%, -50%)' ;
// Create the header
const header = document . createElement ( 'div' ) ;
header . classList . add ( 'card-header' , 'bg-primary' , 'text-white' , 'text-center' , 'p-0' ) ;
// Create the time display in the header
const timeDisplay = document . createElement ( 'div' ) ;
timeDisplay . classList . add ( 'display-4' , 'py-3' , 'mb-0' ) ;
timeDisplay . innerHTML = '<span id="timePickerHour">12</span>:<span id="timePickerMinute">00</span> <span id="timePickerAMPM">PM</span>' ;
header . appendChild ( timeDisplay ) ;
timePicker . appendChild ( header ) ;
// Create the body
const body = document . createElement ( 'div' ) ;
body . classList . add ( 'card-body' , 'bg-white' ) ;
// Create the clock face container
const clockFace = document . createElement ( 'div' ) ;
clockFace . classList . add ( 'time-picker-clock' , 'mb-3' ) ;
clockFace . style . position = 'relative' ;
clockFace . style . width = '250px' ;
clockFace . style . height = '250px' ;
clockFace . style . margin = '0 auto' ;
clockFace . style . backgroundColor = '#f8f9fa' ;
clockFace . style . borderRadius = '50%' ;
// Add clock center dot
const clockCenter = document . createElement ( 'div' ) ;
clockCenter . style . position = 'absolute' ;
clockCenter . style . top = '50%' ;
clockCenter . style . left = '50%' ;
clockCenter . style . transform = 'translate(-50%, -50%)' ;
clockCenter . style . width = '8px' ;
clockCenter . style . height = '8px' ;
clockCenter . style . backgroundColor = '#0d6efd' ;
clockCenter . style . borderRadius = '50%' ;
clockFace . appendChild ( clockCenter ) ;
// Add hour numbers to the clock face
for ( let i = 1 ; i <= 12 ; i ++ ) {
const hourNumber = document . createElement ( 'div' ) ;
hourNumber . classList . add ( 'hour-number' ) ;
hourNumber . textContent = i ;
hourNumber . style . position = 'absolute' ;
hourNumber . style . width = '40px' ;
hourNumber . style . height = '40px' ;
hourNumber . style . textAlign = 'center' ;
hourNumber . style . lineHeight = '40px' ;
hourNumber . style . fontWeight = 'bold' ;
hourNumber . style . cursor = 'pointer' ;
// Calculate position (in a circle)
const angle = ( i * 30 - 90 ) * ( Math . PI / 180 ) ; // 30 degrees per hour, starting at 12 o'clock
const radius = 100 ; // Distance from center
const left = 125 + radius * Math . cos ( angle ) ;
const top = 125 + radius * Math . sin ( angle ) ;
hourNumber . style . left = ` ${ left - 20 } px ` ; // Adjust for element width
hourNumber . style . top = ` ${ top - 20 } px ` ; // Adjust for element height
// Add click event to select hour
hourNumber . addEventListener ( 'click' , ( ) => {
document . getElementById ( 'timePickerHour' ) . textContent = i ;
// Highlight selected hour
document . querySelectorAll ( '.hour-number' ) . forEach ( el => {
el . style . backgroundColor = 'transparent' ;
el . style . color = '#212529' ;
2025-02-21 20:20:12 +00:00
} ) ;
2025-02-25 05:27:29 +00:00
hourNumber . style . backgroundColor = '#0d6efd' ;
hourNumber . style . color = 'white' ;
// Show minute selection
showMinuteSelection ( ) ;
} ) ;
clockFace . appendChild ( hourNumber ) ;
}
// Function to show minute selection
function showMinuteSelection ( ) {
// Hide hour numbers
document . querySelectorAll ( '.hour-number' ) . forEach ( el => {
el . style . display = 'none' ;
} ) ;
// Show minute numbers
document . querySelectorAll ( '.minute-number' ) . forEach ( el => {
el . style . display = 'block' ;
} ) ;
}
// Function to show hour selection
function showHourSelection ( ) {
// Hide minute numbers
document . querySelectorAll ( '.minute-number' ) . forEach ( el => {
el . style . display = 'none' ;
} ) ;
// Show hour numbers
document . querySelectorAll ( '.hour-number' ) . forEach ( el => {
el . style . display = 'block' ;
} ) ;
}
// Add minute numbers to the clock face (initially hidden)
const minuteValues = [ 0 , 5 , 10 , 15 , 20 , 25 , 30 , 35 , 40 , 45 , 50 , 55 ] ;
minuteValues . forEach ( ( minute , index ) => {
const minuteNumber = document . createElement ( 'div' ) ;
minuteNumber . classList . add ( 'minute-number' ) ;
minuteNumber . textContent = minute . toString ( ) . padStart ( 2 , '0' ) ;
minuteNumber . style . position = 'absolute' ;
minuteNumber . style . width = '40px' ;
minuteNumber . style . height = '40px' ;
minuteNumber . style . textAlign = 'center' ;
minuteNumber . style . lineHeight = '40px' ;
minuteNumber . style . fontWeight = 'bold' ;
minuteNumber . style . cursor = 'pointer' ;
minuteNumber . style . display = 'none' ; // Initially hidden
// Calculate position (in a circle)
const angle = ( index * 30 - 90 ) * ( Math . PI / 180 ) ;
const radius = 100 ; // Distance from center
const left = 125 + radius * Math . cos ( angle ) ;
const top = 125 + radius * Math . sin ( angle ) ;
minuteNumber . style . left = ` ${ left - 20 } px ` ; // Adjust for element width
minuteNumber . style . top = ` ${ top - 20 } px ` ; // Adjust for element height
// Add click event to select minute
minuteNumber . addEventListener ( 'click' , ( ) => {
document . getElementById ( 'timePickerMinute' ) . textContent = minute . toString ( ) . padStart ( 2 , '0' ) ;
// Highlight selected minute
document . querySelectorAll ( '.minute-number' ) . forEach ( el => {
el . style . backgroundColor = 'transparent' ;
el . style . color = '#212529' ;
} ) ;
minuteNumber . style . backgroundColor = '#0d6efd' ;
minuteNumber . style . color = 'white' ;
// Show hour selection again
showHourSelection ( ) ;
} ) ;
clockFace . appendChild ( minuteNumber ) ;
} ) ;
body . appendChild ( clockFace ) ;
// Create AM/PM toggle
const ampmToggle = document . createElement ( 'div' ) ;
ampmToggle . classList . add ( 'd-flex' , 'justify-content-center' , 'mb-3' ) ;
const amButton = document . createElement ( 'button' ) ;
amButton . classList . add ( 'btn' , 'btn-outline-primary' , 'me-2' ) ;
amButton . textContent = 'AM' ;
amButton . addEventListener ( 'click' , ( ) => {
document . getElementById ( 'timePickerAMPM' ) . textContent = 'AM' ;
amButton . classList . add ( 'active' ) ;
pmButton . classList . remove ( 'active' ) ;
} ) ;
const pmButton = document . createElement ( 'button' ) ;
pmButton . classList . add ( 'btn' , 'btn-outline-primary' ) ;
pmButton . textContent = 'PM' ;
pmButton . addEventListener ( 'click' , ( ) => {
document . getElementById ( 'timePickerAMPM' ) . textContent = 'PM' ;
pmButton . classList . add ( 'active' ) ;
amButton . classList . remove ( 'active' ) ;
} ) ;
pmButton . classList . add ( 'active' ) ; // Add active class to PM button
ampmToggle . appendChild ( amButton ) ;
ampmToggle . appendChild ( pmButton ) ;
body . appendChild ( ampmToggle ) ;
// Create action buttons
const actionButtons = document . createElement ( 'div' ) ;
actionButtons . classList . add ( 'd-flex' , 'justify-content-between' ) ;
const closeButton = document . createElement ( 'button' ) ;
closeButton . classList . add ( 'btn' , 'btn-outline-secondary' , 'flex-grow-1' , 'me-2' ) ;
closeButton . textContent = 'CANCEL' ;
closeButton . addEventListener ( 'click' , ( ) => {
timePicker . style . display = 'none' ;
} ) ;
const okButton = document . createElement ( 'button' ) ;
okButton . classList . add ( 'btn' , 'btn-primary' , 'flex-grow-1' ) ;
okButton . textContent = 'OK' ;
okButton . addEventListener ( 'click' , ( ) => {
const hour = document . getElementById ( 'timePickerHour' ) . textContent ;
const minute = document . getElementById ( 'timePickerMinute' ) . textContent ;
const ampm = document . getElementById ( 'timePickerAMPM' ) . textContent . toLowerCase ( ) ;
const timeValue = ` ${ hour } : ${ minute } ${ ampm } ` ;
document . getElementById ( 'customTimeInput' ) . value = timeValue ;
// Check if time already exists in available times
if ( availableTimes . includes ( timeValue ) ) {
// Silently handle duplicate time slots - no error message
console . log ( 'Time slot already exists, not adding duplicate' ) ;
} else {
// Automatically add the time slot
addSingleTime ( dateStr , timeValue ) ;
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
// Hide the time picker
timePicker . style . display = 'none' ;
} ) ;
actionButtons . appendChild ( closeButton ) ;
actionButtons . appendChild ( okButton ) ;
body . appendChild ( actionButtons ) ;
timePicker . appendChild ( body ) ;
// Add the time picker to the document
document . body . appendChild ( timePicker ) ;
// Show time picker when button is clicked
timeButton . addEventListener ( 'click' , ( e ) => {
// Remove positioning relative to button
timePicker . style . display = 'block' ;
// Reset to default view (hour selection)
showHourSelection ( ) ;
e . stopPropagation ( ) ;
} ) ;
// Close time picker when clicking outside
document . addEventListener ( 'click' , ( e ) => {
if ( e . target !== timeButton && ! timePicker . contains ( e . target ) ) {
timePicker . style . display = 'none' ;
}
2025-02-21 20:20:12 +00:00
} ) ;
}
// Fetch available dates from the API
async function fetchAvailability ( ) {
try {
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( ! selectedUid ) return { } ;
const response = await fetch ( ` /api/availability/ ${ selectedUid } ` ) ;
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 { } ;
}
}
// Helper function to update calendar UI after availability changes
async function updateAvailabilityUI ( ) {
const availability = await fetchAvailability ( ) ;
if ( selectedAdminDate ) {
await updateTimeSlots ( selectedAdminDate ) ;
}
const calendarDates = document . getElementById ( 'adminCalendarDates' ) ;
if ( calendarDates ) {
const allDateElements = calendarDates . querySelectorAll ( '.date-item' ) ;
2025-02-25 05:27:29 +00:00
const today = new Date ( ) ;
const currentMinutes = today . getHours ( ) * 60 + today . getMinutes ( ) ;
2025-02-21 20:20:12 +00:00
allDateElements . forEach ( dateElement => {
if ( ! dateElement . textContent ) return ; // Skip empty cells
const day = parseInt ( dateElement . textContent ) ;
const date = new Date ( currentAdminYear , currentAdminMonth , day ) ;
const dateStr = date . toISOString ( ) . split ( 'T' ) [ 0 ] ;
2025-02-25 05:27:29 +00:00
// Check if date is in the past
const startOfToday = new Date ( today . getFullYear ( ) , today . getMonth ( ) , today . getDate ( ) ) ;
const isPastDate = date < startOfToday ;
const isToday = date . toDateString ( ) === today . toDateString ( ) ;
// Check if the date has available time slots
let hasAvailableTimes = false ;
2025-02-21 20:20:12 +00:00
if ( availability [ dateStr ] ) {
2025-02-25 05:27:29 +00:00
let times = [ ] ;
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 ( ) ) ;
}
// For today, filter out past times
if ( isToday ) {
times = times . filter ( time => timeToMinutes ( time ) > currentMinutes ) ;
}
// Only mark as available if there are valid times
hasAvailableTimes = times . length > 0 ;
}
// Only add 'available' class if the date is not in the past AND has available times
if ( hasAvailableTimes && ! isPastDate ) {
2025-02-21 20:20:12 +00:00
dateElement . classList . add ( 'available' ) ;
} else {
dateElement . classList . remove ( 'available' ) ;
}
2025-02-25 05:27:29 +00:00
// Always ensure past dates have the disabled class
if ( isPastDate ) {
dateElement . classList . add ( 'disabled' ) ;
dateElement . classList . remove ( 'available' ) ;
}
2025-02-21 20:20:12 +00:00
} ) ;
}
}
// UID management functions
async function loadUids ( ) {
try {
const response = await fetch ( '/api/uids' ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || 'Failed to fetch UIDs' ) ;
}
const uids = await response . json ( ) ;
const select = document . getElementById ( 'uidSelect' ) ;
select . innerHTML = '<option value="">Select a Schedule UID...</option>' ;
uids . forEach ( uidObj => {
const option = document . createElement ( 'option' ) ;
option . value = uidObj . uid ;
option . textContent = uidObj . uid ;
select . appendChild ( option ) ;
} ) ;
} catch ( error ) {
console . error ( 'Error loading UIDs:' , error ) ;
}
}
async function createUid ( uid ) {
try {
const response = await fetch ( '/api/uids' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json'
} ,
body : JSON . stringify ( { uid } )
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || 'Failed to create UID' ) ;
}
// Reload UIDs and select the new one
await loadUids ( ) ;
// Select the new UID and trigger change event
const select = document . getElementById ( 'uidSelect' ) ;
select . value = uid ;
select . dispatchEvent ( new Event ( 'change' ) ) ;
return true ;
} catch ( error ) {
console . error ( 'Error creating UID:' , error ) ;
return false ;
}
}
async function deleteUid ( uid ) {
try {
const response = await fetch ( ` /api/uids/ ${ uid } ` , {
method : 'DELETE'
} ) ;
if ( ! response . ok ) throw new Error ( 'Failed to delete UID' ) ;
await loadUids ( ) ;
return true ;
} catch ( error ) {
console . error ( 'Error deleting UID:' , error ) ;
return false ;
}
}
// Add a single time for the selected date
async function addSingleTime ( dateStr , time ) {
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( ! selectedUid ) {
alert ( 'Please select a UID first' ) ;
return ;
}
try {
const availability = await fetchAvailability ( ) ;
let times = [ ] ;
if ( availability && availability [ dateStr ] ) {
if ( typeof availability [ dateStr ] === 'string' ) {
times = availability [ dateStr ] . split ( ',' ) . map ( t => t . trim ( ) ) ;
} else if ( Array . isArray ( availability [ dateStr ] ) ) {
times = [ ... availability [ dateStr ] ] ;
}
}
times . push ( time ) ;
const response = await fetch ( ` /api/availability/ ${ selectedUid } ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { date : dateStr , times : [ ... new Set ( times ) ] } ) // Remove duplicates
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || ` HTTP error! status: ${ response . status } ` ) ;
}
const result = await response . json ( ) ;
console . log ( result . message ) ;
await updateAvailabilityUI ( ) ;
} catch ( error ) {
console . error ( 'Error adding time slot:' , error ) ;
alert ( 'Failed to add time slot. Please try again.' ) ;
}
}
// Remove a single time for the selected date
async function removeTime ( dateStr , time ) {
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( ! selectedUid ) {
alert ( 'Please select a UID first' ) ;
return ;
}
try {
const availability = await fetchAvailability ( ) ;
let times = [ ] ;
if ( availability && availability [ dateStr ] ) {
if ( typeof availability [ dateStr ] === 'string' ) {
times = availability [ dateStr ] . split ( ',' ) . filter ( t => t . trim ( ) !== time ) ;
} else if ( Array . isArray ( availability [ dateStr ] ) ) {
times = availability [ dateStr ] . filter ( t => t . trim ( ) !== time ) ;
}
}
const response = await fetch ( ` /api/availability/ ${ selectedUid } ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { date : dateStr , times } )
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || ` HTTP error! status: ${ response . status } ` ) ;
}
await updateAvailabilityUI ( ) ;
} catch ( error ) {
console . error ( 'Error removing time slot:' , error ) ;
alert ( 'Failed to remove time slot. Please try again.' ) ;
}
}
// Helper function to convert time string to minutes since midnight
function timeToMinutes ( timeStr ) {
const match = timeStr . match ( /([0-9]+)(?::([0-9]+))?(am|pm)/i ) ;
if ( ! match ) 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 ;
return hours * 60 + minutes ;
}
// Helper function to ensure consistent time format
function formatTimeSlot ( hour ) {
const period = hour < 12 ? 'am' : 'pm' ;
const displayHour = hour === 0 ? 12 : ( hour > 12 ? hour - 12 : hour ) ;
return ` ${ displayHour } :00 ${ period } ` ;
}
2025-02-25 05:27:29 +00:00
document . addEventListener ( 'DOMContentLoaded' , async ( ) => {
// Add custom CSS for the current date to make it more distinguishable
const style = document . createElement ( 'style' ) ;
style . textContent = `
. date - item . current {
border : 2 px solid # ffc107 ! important ; /* Yellow border */
background - color : rgba ( 255 , 193 , 7 , 0.1 ) ! important ; /* Light yellow background */
font - weight : bold ! important ;
color : # 212529 ! important ; /* Ensure text is visible */
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
/* Ensure current date styling takes precedence but doesn't interfere with selection */
. date - item . current . selected {
border : 2 px solid # 0 d6efd ! important ; /* Blue border for selected */
background - color : # 0 d6efd ! important ; /* Dark blue background when selected */
color : white ! important ; /* White text for better contrast */
font - weight : bold ! important ;
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
/* Time picker styling */
# visualTimePicker {
box - shadow : 0 0.5 rem 1 rem rgba ( 0 , 0 , 0 , 0.15 ) ! important ;
border - radius : 8 px ;
overflow : hidden ;
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
# visualTimePicker . card - header {
background - color : # 0 d6efd ;
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
# visualTimePicker . display - 4 {
font - size : 3 rem ;
font - weight : 300 ;
color : white ;
}
. time - picker - clock {
box - shadow : 0 0 10 px rgba ( 0 , 0 , 0 , 0.1 ) ;
}
. hour - number , . minute - number {
border - radius : 50 % ;
transition : all 0.2 s ease ;
}
. hour - number : hover , . minute - number : hover {
background - color : rgba ( 13 , 110 , 253 , 0.1 ) ;
}
/* Time slot pills styling */
. time - slot {
transition : all 0.2 s ease ;
}
. time - slot : hover {
transform : translateY ( - 2 px ) ;
}
/* Make all UID buttons consistent */
# createUid , # deleteUid , # flushDatabase , # copyUrlBtn {
border - radius : 30 px ;
padding : 0.75 rem 1.5 rem ;
font - size : 1.25 rem ;
font - weight : 500 ;
height : 60 px ;
display : flex ;
align - items : center ;
justify - content : center ;
width : 100 % ;
transition : all 0.3 s ease ;
}
# createUid {
background - color : # 2e8 b57 ; /* Money green color */
border - color : # 2e8 b57 ;
}
# createUid : hover {
background - color : # 267349 ;
border - color : # 267349 ;
}
/* URL container styling */
. url - container {
display : flex ;
align - items : center ;
gap : 10 px ;
}
. url - display {
flex - grow : 1 ;
overflow : hidden ;
text - overflow : ellipsis ;
}
` ;
document . head . appendChild ( style ) ;
// Create and add the Copy URL button
const urlContainer = document . querySelector ( '.form-control-lg.bg-light.p-3' ) ;
if ( urlContainer ) {
// Create a wrapper div for the URL and button
const wrapper = document . createElement ( 'div' ) ;
wrapper . classList . add ( 'url-container' , 'mt-2' ) ;
wrapper . style . display = 'flex' ;
wrapper . style . alignItems = 'center' ;
wrapper . style . gap = '10px' ;
wrapper . style . flexWrap = 'nowrap' ; // Prevent wrapping
// Move the existing elements to the wrapper
const uidPlaceholder = document . getElementById ( 'uidPlaceholder' ) ;
const uidUrl = document . getElementById ( 'uidUrl' ) ;
// Create a div to contain the URL display
const urlDisplay = document . createElement ( 'div' ) ;
urlDisplay . classList . add ( 'url-display' ) ;
urlDisplay . style . overflow = 'hidden' ;
urlDisplay . style . textOverflow = 'ellipsis' ;
urlDisplay . style . whiteSpace = 'nowrap' ; // Keep URL on one line
urlDisplay . style . minWidth = '0' ; // Allow flex item to shrink below content size
urlDisplay . style . flexGrow = '1' ; // Take available space
urlDisplay . style . flexShrink = '1' ; // Allow shrinking
// Move the existing elements to the URL display div
if ( uidPlaceholder ) urlDisplay . appendChild ( uidPlaceholder ) ;
if ( uidUrl ) urlDisplay . appendChild ( uidUrl ) ;
// Add the URL display to the wrapper
wrapper . appendChild ( urlDisplay ) ;
// Create the Copy URL button
const copyUrlBtn = document . createElement ( 'button' ) ;
copyUrlBtn . id = 'copyUrlBtn' ;
copyUrlBtn . classList . add ( 'btn' , 'btn-primary' , 'rounded-pill' , 'd-none' ) ;
copyUrlBtn . style . height = '40px' ;
copyUrlBtn . style . padding = '0 15px' ;
copyUrlBtn . style . fontSize = '0.9rem' ;
copyUrlBtn . style . whiteSpace = 'nowrap' ;
copyUrlBtn . style . width = 'auto' ; // Only as wide as content
copyUrlBtn . style . minWidth = 'fit-content' ; // Ensure it fits content
copyUrlBtn . style . flexShrink = '0' ; // Prevent button from shrinking
copyUrlBtn . innerHTML = '<i class="bi bi-clipboard"></i><span style="margin-left: 8px;">Copy URL</span>' ;
// Add click event to copy URL to clipboard
copyUrlBtn . addEventListener ( 'click' , ( ) => {
const url = document . getElementById ( 'uidUrl' ) . href ;
if ( url && url !== '#' ) {
navigator . clipboard . writeText ( url )
. then ( ( ) => {
// Change button text temporarily to indicate success
const originalText = copyUrlBtn . innerHTML ;
copyUrlBtn . innerHTML = '<i class="bi bi-check"></i> Copied!' ;
copyUrlBtn . classList . remove ( 'btn-primary' ) ;
copyUrlBtn . classList . add ( 'btn-success' ) ;
// Reset button after 2 seconds
setTimeout ( ( ) => {
copyUrlBtn . innerHTML = originalText ;
copyUrlBtn . classList . remove ( 'btn-success' ) ;
copyUrlBtn . classList . add ( 'btn-primary' ) ;
} , 2000 ) ;
} )
. catch ( err => {
console . error ( 'Failed to copy URL: ' , err ) ;
alert ( 'Failed to copy URL to clipboard' ) ;
} ) ;
2025-02-21 20:20:12 +00:00
}
} ) ;
2025-02-25 05:27:29 +00:00
// Add the button to the wrapper
wrapper . appendChild ( copyUrlBtn ) ;
// Clear the container and add the wrapper
urlContainer . innerHTML = '' ;
urlContainer . appendChild ( document . createElement ( 'small' ) ) . classList . add ( 'd-block' , 'text-muted' , 'mb-1' ) ;
urlContainer . querySelector ( 'small' ) . textContent = 'Public Access URL:' ;
urlContainer . appendChild ( wrapper ) ;
// Show the button if a UID is already selected
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( selectedUid ) {
copyUrlBtn . classList . remove ( 'd-none' ) ;
}
2025-02-21 20:20:12 +00:00
}
2025-02-25 05:27:29 +00:00
2025-02-21 20:20:12 +00:00
// Load existing UIDs
await loadUids ( ) ;
// Handle UID selection
const uidSelect = document . getElementById ( 'uidSelect' ) ;
const deleteUidBtn = document . getElementById ( 'deleteUid' ) ;
const uidUrl = document . getElementById ( 'uidUrl' ) ;
2025-02-25 05:27:29 +00:00
const uidPlaceholder = document . getElementById ( 'uidPlaceholder' ) ;
2025-02-21 20:20:12 +00:00
uidSelect . addEventListener ( 'change' , async ( ) => {
const selectedUid = uidSelect . value ;
deleteUidBtn . disabled = ! selectedUid ;
flushDatabaseBtn . disabled = ! selectedUid ;
2025-02-25 05:27:29 +00:00
// Show or hide calendar and availability sections based on UID selection
const calendarSection = document . getElementById ( 'calendarSection' ) ;
const calendarDatesSection = document . getElementById ( 'calendarDatesSection' ) ;
const availabilitySection = document . getElementById ( 'availabilitySection' ) ;
if ( selectedUid ) {
calendarSection . classList . remove ( 'd-none' ) ;
calendarDatesSection . classList . remove ( 'd-none' ) ;
availabilitySection . classList . remove ( 'd-none' ) ;
// Show URL link and hide placeholder
uidPlaceholder . classList . add ( 'd-none' ) ;
uidUrl . classList . remove ( 'd-none' ) ;
// Show copy button when URL is displayed
if ( document . getElementById ( 'copyUrlBtn' ) ) {
document . getElementById ( 'copyUrlBtn' ) . classList . remove ( 'd-none' ) ;
}
} else {
calendarSection . classList . add ( 'd-none' ) ;
calendarDatesSection . classList . add ( 'd-none' ) ;
availabilitySection . classList . add ( 'd-none' ) ;
// Hide URL link and show placeholder
uidPlaceholder . classList . remove ( 'd-none' ) ;
uidUrl . classList . add ( 'd-none' ) ;
// Hide copy button when no URL is displayed
if ( document . getElementById ( 'copyUrlBtn' ) ) {
document . getElementById ( 'copyUrlBtn' ) . classList . add ( 'd-none' ) ;
}
}
2025-02-21 20:20:12 +00:00
// Clear current selections
selectedAdminDate = null ;
const selectedDateElem = document . getElementById ( 'selectedDateDisplay' ) ;
const timeSlotsElem = document . getElementById ( 'timeSlots' ) ;
const calendarDatesElem = document . getElementById ( 'adminCalendarDates' ) ;
const adminMonthYear = document . getElementById ( 'adminMonthYear' ) ;
// Clear all UI elements
if ( selectedDateElem ) selectedDateElem . textContent = '' ;
if ( timeSlotsElem ) timeSlotsElem . innerHTML = '' ;
if ( calendarDatesElem ) calendarDatesElem . innerHTML = '' ;
if ( adminMonthYear ) adminMonthYear . textContent = '' ;
if ( selectedUid ) {
const url = ` ${ window . location . origin } / ${ selectedUid } ` ;
uidUrl . textContent = url ;
uidUrl . href = url ;
2025-02-25 05:27:29 +00:00
2025-02-21 20:20:12 +00:00
// Refresh calendar with new UID's data
await renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
2025-02-25 05:27:29 +00:00
// Automatically select today's date and show time slots
const today = new Date ( ) ;
if ( today . getMonth ( ) === currentAdminMonth && today . getFullYear ( ) === currentAdminYear ) {
// Get the first day of the month to pass to selectAdminDate
const firstDay = new Date ( currentAdminYear , currentAdminMonth , 1 ) . getDay ( ) ;
// Select today's date
selectedAdminDate = today ;
await updateTimeSlots ( today ) ;
// Highlight today in the calendar
const dateItems = document . querySelectorAll ( '#adminCalendarDates .date-item' ) ;
dateItems . forEach ( item => item . classList . remove ( 'selected' ) ) ;
// Find today's element in the calendar
const todayDay = today . getDate ( ) ;
const todayElement = Array . from ( dateItems ) . find ( item => {
return item . textContent && parseInt ( item . textContent ) === todayDay ;
} ) ;
if ( todayElement ) {
todayElement . classList . add ( 'selected' ) ;
}
2025-02-21 20:20:12 +00:00
}
} else {
2025-02-25 05:27:29 +00:00
uidUrl . textContent = '' ;
2025-02-21 20:20:12 +00:00
uidUrl . href = '#' ;
// Add placeholder text
if ( selectedDateElem ) selectedDateElem . textContent = 'Please select a UID to view the calendar' ;
await renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
}
} ) ;
// Handle new UID creation
const createUidBtn = document . getElementById ( 'createUid' ) ;
2025-02-25 05:27:29 +00:00
const modalUidInput = document . getElementById ( 'modalUidInput' ) ;
const confirmCreateUidBtn = document . getElementById ( 'confirmCreateUid' ) ;
const createUidModal = new bootstrap . Modal ( document . getElementById ( 'createUidModal' ) ) ;
// Add event listener for modal shown event to focus the input field
document . getElementById ( 'createUidModal' ) . addEventListener ( 'shown.bs.modal' , function ( ) {
modalUidInput . focus ( ) ;
} ) ;
createUidBtn . addEventListener ( 'click' , ( ) => {
modalUidInput . value = '' ;
createUidModal . show ( ) ;
} ) ;
2025-02-21 20:20:12 +00:00
2025-02-25 05:27:29 +00:00
// Add event listener for Enter key on the input field
modalUidInput . addEventListener ( 'keyup' , ( event ) => {
if ( event . key === 'Enter' ) {
confirmCreateUidBtn . click ( ) ;
}
} ) ;
confirmCreateUidBtn . addEventListener ( 'click' , async ( ) => {
const uid = modalUidInput . value . trim ( ) . toLowerCase ( ) ;
2025-02-21 20:20:12 +00:00
if ( ! /^[a-z0-9-]+$/ . test ( uid ) ) {
alert ( 'UID can only contain lowercase letters, numbers, and hyphens' ) ;
return ;
}
if ( await createUid ( uid ) ) {
2025-02-25 05:27:29 +00:00
createUidModal . hide ( ) ;
2025-02-21 20:20:12 +00:00
} else {
alert ( 'Failed to create UID. Please try again.' ) ;
}
} ) ;
// Handle UID deletion
deleteUidBtn . addEventListener ( 'click' , async ( ) => {
const uid = uidSelect . value ;
if ( ! uid ) return ;
if ( ! confirm ( ` Are you sure you want to delete UID: ${ uid } ? \n This will remove all associated availability data. ` ) ) {
return ;
}
if ( await deleteUid ( uid ) ) {
// Clear selection and trigger change event
uidSelect . value = '' ;
uidSelect . dispatchEvent ( new Event ( 'change' ) ) ;
} else {
alert ( 'Failed to delete UID. Please try again.' ) ;
}
} ) ;
// Initialize UI elements
const adminCalendarDates = document . getElementById ( 'adminCalendarDates' ) ;
const adminMonthYear = document . getElementById ( 'adminMonthYear' ) ;
const adminPrevMonthBtn = document . getElementById ( 'adminPrevMonth' ) ;
const adminNextMonthBtn = document . getElementById ( 'adminNextMonth' ) ;
const selectedDateDisplay = document . getElementById ( 'selectedDateDisplay' ) ;
const timeSlots = document . getElementById ( 'timeSlots' ) ;
const flushDatabaseBtn = document . getElementById ( 'flushDatabase' ) ;
const resetDatabaseBtn = document . getElementById ( 'resetDatabase' ) ;
2025-02-25 05:27:29 +00:00
const devToolsBtn = document . getElementById ( 'devToolsBtn' ) ;
const devToolsModal = new bootstrap . Modal ( document . getElementById ( 'devToolsModal' ) ) ;
// Show Dev Tools modal when button is clicked
devToolsBtn . addEventListener ( 'click' , ( ) => {
devToolsModal . show ( ) ;
} ) ;
2025-02-21 20:20:12 +00:00
// Handle database reset
resetDatabaseBtn . addEventListener ( 'click' , async ( ) => {
if ( ! confirm ( '⚠️ WARNING: This will permanently delete ALL UIDs and their associated availability data. This action cannot be undone.\n\nAre you absolutely sure you want to proceed?' ) ) {
return ;
}
try {
const response = await fetch ( '/api/reset' , {
method : 'POST'
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || 'Failed to reset database' ) ;
}
// Reload the page to reset all state
window . location . reload ( ) ;
} catch ( error ) {
console . error ( 'Error resetting database:' , error ) ;
alert ( 'Failed to reset database. Please try again.' ) ;
}
} ) ;
// Check if a date has availability
async function checkAvailabilityForDate ( date ) {
const availability = await fetchAvailability ( ) ;
const dateStr = date . toISOString ( ) . split ( 'T' ) [ 0 ] ;
return ! ! availability [ dateStr ] ;
}
// Flush (delete) all entries from the database
flushDatabaseBtn . addEventListener ( 'click' , async ( ) => {
const selectedUid = document . getElementById ( 'uidSelect' ) . value ;
if ( ! selectedUid ) {
alert ( 'Please select a UID first' ) ;
return ;
}
if ( ! confirm ( ` Are you sure you want to delete ALL availability entries for UID: ${ selectedUid } ? ` ) ) {
return ;
}
try {
console . log ( ` Attempting to delete all availability for UID: ${ selectedUid } ... ` ) ;
const response = await fetch ( ` /api/availability/ ${ selectedUid } /flush ` , {
method : 'DELETE' ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || ` HTTP error! status: ${ response . status } ` ) ;
}
const result = await response . json ( ) ;
console . log ( 'Delete availability response:' , result ) ;
// Refresh data
await fetchAvailability ( ) ; // Refresh availability data
// Reset UI
selectedAdminDate = null ; // Clear selected date
selectedDateDisplay . textContent = '' ;
timeSlots . innerHTML = '' ; // Clear time slots
await renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
} catch ( error ) {
console . error ( 'Error deleting availability:' , error ) ;
alert ( 'Failed to delete availability. Please try again.' ) ;
}
} ) ;
// Navigate to previous month (admin)
adminPrevMonthBtn . addEventListener ( 'click' , ( ) => {
currentAdminMonth -- ;
if ( currentAdminMonth < 0 ) {
currentAdminMonth = 11 ;
currentAdminYear -- ;
}
renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
} ) ;
// Navigate to next month (admin)
adminNextMonthBtn . addEventListener ( 'click' , ( ) => {
currentAdminMonth ++ ;
if ( currentAdminMonth > 11 ) {
currentAdminMonth = 0 ;
currentAdminYear ++ ;
}
renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
} ) ;
// Handle global database flush
const flushGlobalDatabaseBtn = document . getElementById ( 'flushGlobalDatabase' ) ;
if ( flushGlobalDatabaseBtn ) {
flushGlobalDatabaseBtn . addEventListener ( 'click' , async ( ) => {
if ( ! confirm ( 'WARNING: This will delete ALL UIDs and their availability data. This action cannot be undone. Are you sure you want to proceed?' ) ) {
return ;
}
try {
const response = await fetch ( '/api/flush-global' , {
method : 'DELETE' ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
if ( ! response . ok ) {
const errorData = await response . json ( ) ;
throw new Error ( errorData . error || ` HTTP error! status: ${ response . status } ` ) ;
}
const result = await response . json ( ) ;
console . log ( 'Global database flush response:' , result ) ;
alert ( result . message || 'Database flushed successfully!' ) ;
// Reset everything
selectedAdminDate = null ;
const selectedDateElem = document . getElementById ( 'selectedDateDisplay' ) ;
const timeSlotsElem = document . getElementById ( 'timeSlots' ) ;
const calendarDatesElem = document . getElementById ( 'adminCalendarDates' ) ;
if ( selectedDateElem ) selectedDateElem . textContent = '' ;
if ( timeSlotsElem ) timeSlotsElem . innerHTML = '' ;
if ( calendarDatesElem ) calendarDatesElem . innerHTML = '' ;
// Reload UIDs
await loadUids ( ) ;
// Update UID URL display
const uidUrl = document . getElementById ( 'uidUrl' ) ;
if ( uidUrl ) uidUrl . textContent = 'Select a UID first' ;
// Disable delete button
const deleteUidBtn = document . getElementById ( 'deleteUid' ) ;
if ( deleteUidBtn ) deleteUidBtn . disabled = true ;
} catch ( error ) {
console . error ( 'Error flushing database:' , error ) ;
alert ( 'Failed to flush database. Please try again.' ) ;
}
} ) ;
}
// Initial render with current date
renderAdminCalendar ( currentAdminMonth , currentAdminYear ) ;
if ( currentAdminDate . getDate ( ) <= new Date ( currentAdminYear , currentAdminMonth , 0 ) . getDate ( ) ) {
const hasAvailability = await checkAvailabilityForDate ( currentAdminDate ) ;
if ( hasAvailability ) {
selectAdminDate ( currentAdminDate , new Date ( currentAdminYear , currentAdminMonth , 1 ) . getDay ( ) ) ;
}
}
} ) ;