'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Calendar, Clock, MapPin, ChevronLeft, ChevronRight, Users, User } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; interface Court { id: string; name: string; isActive: boolean; } interface Booking { id: string; courtId: string; date: string; startTime: string; endTime: string; status: string; userId: string; notes?: string; } interface BookingSlot { time: string; courtId: string; courtName: string; available: boolean; bookingId?: string; } interface Settings { booking_window_days: string; booking_start_time: string; booking_end_time: string; allow_weekend_bookings: string; } export function EnhancedBookingCalendar() { const [selectedDate, setSelectedDate] = useState(new Date()); const [courts, setCourts] = useState([]); const [bookings, setBookings] = useState([]); const [bookingSlots, setBookingSlots] = useState([]); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(false); const [partnerName, setPartnerName] = useState(''); const [notes, setNotes] = useState(''); const [showBookingDialog, setShowBookingDialog] = useState(false); const [selectedSlot, setSelectedSlot] = useState(null); const { toast } = useToast(); useEffect(() => { fetchSettings(); fetchCourts(); }, []); useEffect(() => { if (courts.length > 0 && settings) { fetchBookings(); } }, [selectedDate, courts, settings]); // Fetch settings from public endpoint (not admin) const fetchSettings = async () => { try { const response = await fetch('/api/settings'); if (response.ok) { const data = await response.json(); const settingsMap: Settings = { booking_window_days: '7', booking_start_time: '08:00', booking_end_time: '22:00', allow_weekend_bookings: 'true', }; data.settings.forEach((setting: any) => { if (setting.key in settingsMap) { settingsMap[setting.key as keyof Settings] = setting.value; } }); setSettings(settingsMap); } else { // If settings fetch fails, use defaults setSettings({ booking_window_days: '7', booking_start_time: '08:00', booking_end_time: '22:00', allow_weekend_bookings: 'true', }); } } catch (error) { console.error('Error fetching settings:', error); // Set default settings setSettings({ booking_window_days: '7', booking_start_time: '08:00', booking_end_time: '22:00', allow_weekend_bookings: 'true', }); } }; // Fetch courts from public endpoint (not admin) const fetchCourts = async () => { try { const response = await fetch('/api/courts'); if (response.ok) { const data = await response.json(); setCourts(data.courts.filter((court: Court) => court.isActive)); } } catch (error) { console.error('Error fetching courts:', error); toast({ title: 'Error', description: 'Failed to fetch courts', variant: 'destructive', }); } }; const fetchBookings = async () => { try { const response = await fetch('/api/bookings'); if (response.ok) { const data = await response.json(); setBookings(data.bookings); generateBookingSlots(data.bookings); } } catch (error) { console.error('Error fetching bookings:', error); toast({ title: 'Error', description: 'Failed to fetch bookings', variant: 'destructive', }); } }; const generateTimeSlots = (): string[] => { if (!settings) return []; const start = parseInt(settings.booking_start_time.split(':')[0]); const end = parseInt(settings.booking_end_time.split(':')[0]); const slots = []; for (let hour = start; hour < end; hour++) { slots.push(`${hour.toString().padStart(2, '0')}:00`); } return slots; }; const generateBookingSlots = (existingBookings: Booking[]) => { const dateStr = selectedDate.toISOString().split('T')[0]; const timeSlots = generateTimeSlots(); const slots: BookingSlot[] = []; courts.forEach((court) => { timeSlots.forEach((time) => { const existingBooking = existingBookings.find( (booking) => booking.courtId === court.id && booking.date === dateStr && booking.startTime === time && booking.status === 'active' ); slots.push({ time, courtId: court.id, courtName: court.name, available: !existingBooking, bookingId: existingBooking?.id, }); }); }); setBookingSlots(slots); }; const isDateSelectable = (date: Date): boolean => { if (!settings) return false; const today = new Date(); today.setHours(0, 0, 0, 0); const selectedDateOnly = new Date(date); selectedDateOnly.setHours(0, 0, 0, 0); // Check if date is in the past if (selectedDateOnly < today) return false; // Check booking window const maxDate = new Date(today); maxDate.setDate(today.getDate() + parseInt(settings.booking_window_days)); if (selectedDateOnly > maxDate) return false; // Check weekend restrictions if (settings.allow_weekend_bookings === 'false') { const dayOfWeek = selectedDateOnly.getDay(); if (dayOfWeek === 0 || dayOfWeek === 6) return false; // Sunday or Saturday } return true; }; const handleSlotClick = (slot: BookingSlot) => { if (!slot.available) return; setSelectedSlot(slot); setPartnerName(''); setNotes(''); setShowBookingDialog(true); }; const handleBookingConfirm = async () => { if (!selectedSlot) return; setLoading(true); try { const dateStr = selectedDate.toISOString().split('T')[0]; const bookingNotes = []; if (partnerName.trim()) { bookingNotes.push(`Partner: ${partnerName.trim()}`); } if (notes.trim()) { bookingNotes.push(notes.trim()); } const response = await fetch('/api/bookings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ courtId: selectedSlot.courtId, date: dateStr, timeSlot: selectedSlot.time, notes: bookingNotes.join(' | '), }), }); const data = await response.json(); if (response.ok) { toast({ title: 'Success', description: 'Booking created successfully!', }); setShowBookingDialog(false); fetchBookings(); // Refresh bookings } else { toast({ title: 'Error', description: data.error || 'Failed to create booking', variant: 'destructive', }); } } catch (error) { console.error('Error booking slot:', error); toast({ title: 'Error', description: 'Failed to create booking', variant: 'destructive', }); } finally { setLoading(false); } }; const navigateDate = (direction: 'prev' | 'next') => { const newDate = new Date(selectedDate); newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1)); if (isDateSelectable(newDate)) { setSelectedDate(newDate); } }; const getAvailableDates = (): Date[] => { if (!settings) return []; const dates: Date[] = []; const today = new Date(); const maxDays = parseInt(settings.booking_window_days); for (let i = 0; i <= maxDays; i++) { const date = new Date(today); date.setDate(today.getDate() + i); if (isDateSelectable(date)) { dates.push(date); } } return dates; }; const isPastDate = (date: Date) => { const today = new Date(); today.setHours(0, 0, 0, 0); return date < today; }; const isToday = (date: Date) => { const today = new Date(); return date.toDateString() === today.toDateString(); }; if (!settings) { return (

Loading booking system...

); } return (
Book Your Court {/* Mobile-friendly date navigation */}
{/* Quick Date Selection */}

Select Date

{getAvailableDates() .slice(0, 8) .map((date, index) => ( ))}
{/* Selected Date Display */}

{selectedDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', })}

{isToday(selectedDate) && Today}
{/* Loading State */} {loading && (

Loading booking slots...

)} {/* No Courts Available */} {!loading && courts.length === 0 && (

No courts available for booking

)} {/* Time Slots Grid */} {!loading && courts.length > 0 && (

Available Time Slots

{bookingSlots.map((slot, index) => (
handleSlotClick(slot)} >
{slot.time} -{' '} {String(parseInt(slot.time.split(':')[0]) + 1).padStart(2, '0')} :00
{slot.courtName}
{!slot.available && (
Already booked
)}
))}
)} {/* No Slots Message */} {!loading && courts.length > 0 && bookingSlots.length === 0 && (

No booking slots available for this date

)}
{/* Booking Dialog */} Confirm Your Booking
{selectedSlot && (
{selectedDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', })}
{selectedSlot.time} -{' '} {String(parseInt(selectedSlot.time.split(':')[0]) + 1).padStart(2, '0')}:00
{selectedSlot.courtName}
)}
setPartnerName(e.target.value)} className='pl-10' />

Enter the name of the person you'll be playing with