import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; interface RouteContext { params: Promise<{ id: string }>; } interface TimeSlot { time: string; available: boolean; price: number; bookingId?: string; } // Helper function to parse time string (HH:MM) to minutes since midnight function timeToMinutes(time: string): number { const [hours, minutes] = time.split(':').map(Number); return hours * 60 + minutes; } // Helper function to format minutes to HH:MM function minutesToTime(minutes: number): string { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`; } // Helper function to check if a time is premium (after 18:00 or weekend) function isPremiumTime(date: Date, timeMinutes: number): boolean { const dayOfWeek = date.getDay(); // 0 = Sunday, 6 = Saturday const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; const isEveningTime = timeMinutes >= 18 * 60; // After 18:00 return isWeekend || isEveningTime; } // GET /api/courts/[id]/availability - Get availability for a court on a specific date export async function GET( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const { id: courtId } = await context.params; const { searchParams } = new URL(request.url); const dateParam = searchParams.get('date'); // Validate date parameter if (!dateParam) { return NextResponse.json( { error: 'Missing required query parameter: date (YYYY-MM-DD format)' }, { status: 400 } ); } // Parse and validate date format const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateParam)) { return NextResponse.json( { error: 'Invalid date format. Use YYYY-MM-DD' }, { status: 400 } ); } const requestedDate = new Date(dateParam); if (isNaN(requestedDate.getTime())) { return NextResponse.json( { error: 'Invalid date value' }, { status: 400 } ); } // Fetch court with site info const court = await db.court.findFirst({ where: { id: courtId, site: { organizationId: session.user.organizationId, }, }, include: { site: { select: { id: true, name: true, openTime: true, closeTime: true, timezone: true, }, }, }, }); if (!court) { return NextResponse.json( { error: 'Court not found' }, { status: 404 } ); } // Get the start and end of the requested date const startOfDay = new Date(dateParam); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(dateParam); endOfDay.setHours(23, 59, 59, 999); // Fetch existing bookings for this court on the specified date const bookings = await db.booking.findMany({ where: { courtId, status: { in: ['PENDING', 'CONFIRMED'], }, startTime: { gte: startOfDay, lte: endOfDay, }, }, select: { id: true, startTime: true, endTime: true, }, orderBy: { startTime: 'asc', }, }); // Create a map of booked time slots const bookedSlots = new Map(); bookings.forEach((booking) => { const startHour = booking.startTime.getHours(); const startMinutes = booking.startTime.getMinutes(); const timeKey = `${startHour.toString().padStart(2, '0')}:${startMinutes.toString().padStart(2, '0')}`; bookedSlots.set(timeKey, booking.id); }); // Generate time slots from open to close (1 hour each) const openMinutes = timeToMinutes(court.site.openTime); const closeMinutes = timeToMinutes(court.site.closeTime); const slots: TimeSlot[] = []; const basePrice = Number(court.pricePerHour); const premiumMultiplier = 1.25; // 25% premium for evening/weekend for (let minutes = openMinutes; minutes < closeMinutes; minutes += 60) { const time = minutesToTime(minutes); const bookingId = bookedSlots.get(time); const isPremium = isPremiumTime(requestedDate, minutes); const price = isPremium ? basePrice * premiumMultiplier : basePrice; slots.push({ time, available: !bookingId && court.status === 'AVAILABLE' && court.isActive, price: Math.round(price * 100) / 100, // Round to 2 decimal places ...(bookingId && { bookingId }), }); } return NextResponse.json({ court: { id: court.id, name: court.name, type: court.type, status: court.status, isActive: court.isActive, site: court.site, }, date: dateParam, slots, }); } catch (error) { console.error('Error fetching court availability:', error); return NextResponse.json( { error: 'Failed to fetch court availability' }, { status: 500 } ); } }