"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardFooter, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { cn, formatTime, formatCurrency, formatDate } from "@/lib/utils"; import type { SelectedSlot } from "./booking-calendar"; interface Client { id: string; firstName: string; lastName: string; email: string | null; phone: string | null; level: string | null; memberships: Array<{ id: string; status: string; remainingHours: number | null; plan: { id: string; name: string; discountPercent: number | null; }; }>; } interface ClientsResponse { data: Client[]; pagination: { total: number; limit: number; offset: number; hasMore: boolean; }; } interface BookingInfo { id: string; startTime: string; endTime: string; status: string; totalPrice: number; paymentType: string; client: { id: string; firstName: string; lastName: string; email: string | null; phone: string | null; }; court: { id: string; name: string; type: string; }; } interface BookingDialogProps { courtId: string; date: string; slot: SelectedSlot; onClose: () => void; onBookingCreated?: () => void; onBookingCancelled?: () => void; } export function BookingDialog({ courtId, date, slot, onClose, onBookingCreated, onBookingCancelled, }: BookingDialogProps) { const [searchQuery, setSearchQuery] = useState(""); const [clients, setClients] = useState([]); const [selectedClient, setSelectedClient] = useState(null); const [loadingClients, setLoadingClients] = useState(false); const [loadingBooking, setLoadingBooking] = useState(false); const [booking, setBooking] = useState(null); const [loadingBookingInfo, setLoadingBookingInfo] = useState(false); const [error, setError] = useState(null); const [creatingBooking, setCreatingBooking] = useState(false); const [cancellingBooking, setCancellingBooking] = useState(false); // Parse slot time to Date for display const [hours, minutes] = slot.time.split(":").map(Number); const slotDate = new Date(date); slotDate.setHours(hours, minutes, 0, 0); // Fetch booking info if slot is not available const fetchBookingInfo = useCallback(async () => { if (!slot.bookingId) return; setLoadingBookingInfo(true); try { const response = await fetch(`/api/bookings/${slot.bookingId}`); if (!response.ok) { throw new Error("Error loading booking"); } const data = await response.json(); setBooking(data); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); } finally { setLoadingBookingInfo(false); } }, [slot.bookingId]); // Fetch clients based on search const fetchClients = useCallback(async (search: string) => { if (!search || search.trim().length < 2) { setClients([]); return; } setLoadingClients(true); try { const response = await fetch( `/api/clients?search=${encodeURIComponent(search)}&limit=10` ); if (!response.ok) { throw new Error("Error searching players"); } const data: ClientsResponse = await response.json(); setClients(data.data); } catch (err) { console.error("Error fetching clients:", err); setClients([]); } finally { setLoadingClients(false); } }, []); // Load booking info on mount if slot is booked useEffect(() => { if (!slot.available && slot.bookingId) { fetchBookingInfo(); } }, [slot.available, slot.bookingId, fetchBookingInfo]); // Debounce client search useEffect(() => { const timer = setTimeout(() => { fetchClients(searchQuery); }, 300); return () => clearTimeout(timer); }, [searchQuery, fetchClients]); // Handle booking creation const handleCreateBooking = async () => { if (!selectedClient) return; setCreatingBooking(true); setError(null); try { // Calculate end time (1 hour later) const endDate = new Date(slotDate); endDate.setHours(endDate.getHours() + 1); const response = await fetch("/api/bookings", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ courtId, clientId: selectedClient.id, startTime: slotDate.toISOString(), endTime: endDate.toISOString(), paymentType: "CASH", }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Error creating booking"); } onBookingCreated?.(); onClose(); } catch (err) { setError(err instanceof Error ? err.message : "Error creating booking"); } finally { setCreatingBooking(false); } }; // Handle booking cancellation const handleCancelBooking = async () => { if (!slot.bookingId) return; setCancellingBooking(true); setError(null); try { const response = await fetch(`/api/bookings/${slot.bookingId}`, { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ cancelReason: "Cancelled by administrator", }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Error cancelling booking"); } onBookingCancelled?.(); onClose(); } catch (err) { setError( err instanceof Error ? err.message : "Error cancelling booking" ); } finally { setCancellingBooking(false); } }; // Handle click outside to close const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; return (
{slot.available ? "New Booking" : "Booking Details"}

Court: {slot.courtName}

Date: {formatDate(date)}

Time: {formatTime(slotDate)}

Price:{" "} {formatCurrency(slot.price)}

{error && (
{error}
)} {/* Available slot - show client search */} {slot.available && (
setSearchQuery(e.target.value)} autoFocus />
{/* Client search results */}
{loadingClients && (
)} {!loadingClients && searchQuery.length >= 2 && clients.length === 0 && (

No players found.

)} {!loadingClients && clients.map((client) => ( ))}
{/* Selected client summary */} {selectedClient && (

Selected player:

{selectedClient.firstName} {selectedClient.lastName}

{selectedClient.memberships.length > 0 && (

Membership: {selectedClient.memberships[0].plan.name} {selectedClient.memberships[0].remainingHours !== null && selectedClient.memberships[0].remainingHours > 0 && ( ({selectedClient.memberships[0].remainingHours}h remaining) )}

)}
)}
)} {/* Booked slot - show booking info */} {!slot.available && (
{loadingBookingInfo && (
)} {!loadingBookingInfo && booking && (

Player

{booking.client.firstName} {booking.client.lastName}

{booking.client.phone && (

{booking.client.phone}

)} {booking.client.email && (

{booking.client.email}

)}

Status

{booking.status === "CONFIRMED" && "Confirmed"} {booking.status === "PENDING" && "Pending"} {booking.status === "CANCELLED" && "Cancelled"} {booking.status === "COMPLETED" && "Completed"} {booking.status === "NO_SHOW" && "No Show"}

Payment Type

{booking.paymentType === "CASH" && "Cash"} {booking.paymentType === "CARD" && "Card"} {booking.paymentType === "TRANSFER" && "Transfer"} {booking.paymentType === "MEMBERSHIP" && "Membership"} {booking.paymentType === "FREE" && "Free"}

Total

{formatCurrency(Number(booking.totalPrice))}

)} {!loadingBookingInfo && !booking && (

Could not load booking information.

)}
)}
{slot.available && ( )} {!slot.available && booking && booking.status !== "CANCELLED" && ( )}
); }