import React, { useMemo } from 'react'; import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Loader2, XCircle } from 'lucide-react'; import { usePublicAvailability, usePublicBusinessHours } from '../../hooks/useBooking'; import { formatTimeForDisplay, getTimezoneAbbreviation, getUserTimezone } from '../../utils/dateUtils'; interface DateTimeSelectionProps { serviceId?: number; selectedDate: Date | null; selectedTimeSlot: string | null; selectedAddonIds?: number[]; onDateChange: (date: Date) => void; onTimeChange: (time: string) => void; } export const DateTimeSelection: React.FC = ({ serviceId, selectedDate, selectedTimeSlot, selectedAddonIds = [], onDateChange, onTimeChange }) => { const today = new Date(); const [currentMonth, setCurrentMonth] = React.useState(today.getMonth()); const [currentYear, setCurrentYear] = React.useState(today.getFullYear()); // Calculate date range for business hours query (current month view) const { startDate, endDate } = useMemo(() => { const start = new Date(currentYear, currentMonth, 1); const end = new Date(currentYear, currentMonth + 1, 0); return { startDate: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-01`, endDate: `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, '0')}-${String(end.getDate()).padStart(2, '0')}` }; }, [currentMonth, currentYear]); // Fetch business hours for the month const { data: businessHours, isLoading: businessHoursLoading } = usePublicBusinessHours(startDate, endDate); // Create a map of dates to their open status const openDaysMap = useMemo(() => { const map = new Map(); if (businessHours?.dates) { businessHours.dates.forEach(day => { map.set(day.date, day.is_open); }); } return map; }, [businessHours]); // Format selected date for API query (YYYY-MM-DD) const dateString = selectedDate ? `${selectedDate.getFullYear()}-${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}` : undefined; // Fetch availability when both serviceId and date are set // Pass addon IDs to check availability for addon resources too const { data: availability, isLoading: availabilityLoading, isError, error } = usePublicAvailability( serviceId, dateString, selectedAddonIds.length > 0 ? selectedAddonIds : undefined ); const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay(); const handlePrevMonth = () => { if (currentMonth === 0) { setCurrentMonth(11); setCurrentYear(currentYear - 1); } else { setCurrentMonth(currentMonth - 1); } }; const handleNextMonth = () => { if (currentMonth === 11) { setCurrentMonth(0); setCurrentYear(currentYear + 1); } else { setCurrentMonth(currentMonth + 1); } }; const days = Array.from({ length: daysInMonth }, (_, i) => i + 1); const monthName = new Date(currentYear, currentMonth).toLocaleString('default', { month: 'long' }); const isSelected = (day: number) => { return selectedDate?.getDate() === day && selectedDate?.getMonth() === currentMonth && selectedDate?.getFullYear() === currentYear; }; const isPast = (day: number) => { const d = new Date(currentYear, currentMonth, day); const now = new Date(); now.setHours(0, 0, 0, 0); return d < now; }; const isClosed = (day: number) => { const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; // If we have business hours data, use it. Otherwise default to open (except past dates) if (openDaysMap.size > 0) { return openDaysMap.get(dateStr) === false; } return false; }; const isDisabled = (day: number) => { return isPast(day) || isClosed(day); }; return (
{/* Calendar Section */}

Select Date

{monthName} {currentYear}
Sun
Mon
Tue
Wed
Thu
Fri
Sat
{businessHoursLoading ? (
) : (
{Array.from({ length: firstDayOfMonth }).map((_, i) => (
))} {days.map((day) => { const past = isPast(day); const closed = isClosed(day); const disabled = isDisabled(day); const selected = isSelected(day); return ( ); })}
)} {/* Legend */}
Closed
Selected
{/* Time Slots Section */}

Available Time Slots

{!selectedDate ? (
Please select a date first
) : availabilityLoading ? (
) : isError ? (

Failed to load availability

{error instanceof Error ? error.message : 'Please try again'}

) : availability?.is_open === false ? (

Business Closed

Please select another date

) : availability?.slots && availability.slots.length > 0 ? ( <> {(() => { // Determine which timezone to display based on business settings const displayTimezone = availability.timezone_display_mode === 'viewer' ? getUserTimezone() : availability.business_timezone || getUserTimezone(); const tzAbbrev = getTimezoneAbbreviation(displayTimezone); return ( <>

{availability.business_hours && ( <>Business hours: {availability.business_hours.start} - {availability.business_hours.end} • )} Times shown in {tzAbbrev}

{availability.slots.map((slot) => { // Format time in the appropriate timezone const displayTime = formatTimeForDisplay( slot.time, availability.timezone_display_mode === 'viewer' ? null : availability.business_timezone ); return ( ); })}
); })()} ) : !serviceId ? (
Please select a service first
) : (
No available time slots for this date
)}
); };