import { Button } from '@/components/ui/button'; import { DatePicker } from '@/components/ui/date-picker'; import { Field, FieldError, FieldLabel } from '@/components/ui/field'; import { Input } from '@/components/ui/input'; import { ResponsiveDialog, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, } from '@/components/ui/responsive-dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { zodResolver } from '@hookform/resolvers/zod'; import { useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { useBooking } from '../booking-provider'; import { fromIsoDateLocal, getDayOfWeek, getNowTime, isTodayIso, minutesToTime, timeToMinutes, toIsoDateLocal, } from '../lib/booking-time'; const bookingFormSchema = z.object({ customerName: z .string() .trim() .min(2, 'Ingresa un nombre válido.') .max(120, 'El nombre no puede superar los 120 caracteres.'), customerPhone: z .string() .trim() .min(6, 'Ingresa un telefono válido.') .max(30, 'El telefono no puede superar los 30 caracteres.'), customerEmail: z.string().email('Ingresá un email válido.').or(z.literal('')), }); type BookingForm = z.infer; export function BookingCreateDialog() { const { courts, bookings, selectedDate, selectedSlot, isCreateBookingOpen, createBookingError, isCreatingBooking, closeCreateBooking, createBooking, createRecurringBooking, isRecurringEnabled, } = useBooking(); const [date, setDate] = useState(selectedSlot?.date ?? selectedDate); const [sportId, setSportId] = useState(selectedSlot?.sportId ?? 'all'); const [courtId, setCourtId] = useState(selectedSlot?.courtId ?? ''); const [startTime, setStartTime] = useState(selectedSlot?.startTime ?? ''); const [isRecurring, setIsRecurring] = useState(false); const [recurringEndDate, setRecurringEndDate] = useState(undefined); const { register, handleSubmit, reset, setFocus, formState: { errors, isValid }, } = useForm({ resolver: zodResolver(bookingFormSchema), mode: 'onChange', defaultValues: { customerName: '', customerPhone: '', customerEmail: '', }, }); useEffect(() => { if (!isCreateBookingOpen) return; setDate(selectedSlot?.date ?? selectedDate); setSportId(selectedSlot?.sportId ?? 'all'); setCourtId(selectedSlot?.courtId ?? ''); setStartTime(selectedSlot?.startTime ?? ''); setIsRecurring(false); setRecurringEndDate(undefined); reset(); if (selectedSlot?.courtId && selectedSlot?.startTime) { requestAnimationFrame(() => { setFocus('customerName'); }); } }, [isCreateBookingOpen, reset, selectedDate, selectedSlot, setFocus]); const sports = [...new Map(courts.map((court) => [court.sport.id, court.sport])).values()]; const filteredCourts = useMemo(() => { if (sportId === 'all') return courts; return courts.filter((court) => court.sportId === sportId); }, [courts, sportId]); const selectedCourt = courts.find((court) => court.id === courtId); const availableStartTimes = useMemo(() => { if (!selectedCourt) return []; const times: string[] = []; const dayOfWeek = getDayOfWeek(date); const dayAvailability = selectedCourt.availability .filter((range) => range.dayOfWeek === dayOfWeek) .sort((a, b) => timeToMinutes(a.startTime) - timeToMinutes(b.startTime)); const courtBookings = bookings.filter( (booking) => booking.date === date && booking.courtId === selectedCourt.id && booking.status !== 'CANCELLED' ); for (const range of dayAvailability) { const start = timeToMinutes(range.startTime); const end = timeToMinutes(range.endTime); for ( let minute = start; minute + selectedCourt.slotDurationMinutes <= end; minute += selectedCourt.slotDurationMinutes ) { const slotEnd = minute + selectedCourt.slotDurationMinutes; if (isTodayIso(date) && minute <= timeToMinutes(getNowTime())) continue; const isBooked = courtBookings.some((booking) => { const bookingStart = timeToMinutes(booking.startTime); const bookingEnd = timeToMinutes(booking.endTime); return minute < bookingEnd && slotEnd > bookingStart; }); if (!isBooked) { times.push(minutesToTime(minute)); } } } return times; }, [bookings, date, selectedCourt]); useEffect(() => { if (startTime && !availableStartTimes.includes(startTime)) { setStartTime(''); } }, [availableStartTimes, startTime]); const onSubmit = async (values: BookingForm) => { if (isRecurring) { await createRecurringBooking({ courtId, date, startTime, customerName: values.customerName, customerPhone: values.customerPhone, customerEmail: values.customerEmail, recurringEndDate, }); } else { await createBooking({ courtId, date, startTime, customerName: values.customerName, customerPhone: values.customerPhone, customerEmail: values.customerEmail, }); } }; return ( !open && closeCreateBooking()} > event.preventDefault()} > Nueva reserva Crea un turno para atención telefónica o mostrador.
Fecha { setDate(nextDate ? toIsoDateLocal(nextDate) : selectedDate); }} /> Deporte Cancha Horario
{isRecurringEnabled && ( <>

Repetir todas las semanas

{isRecurring ? `Se creará una reserva todos los ${getDayOfWeekLabel(date)} a las ${startTime || '...'}` : 'Crea turnos fijos en el mismo horario'}

{isRecurring && ( Repetir hasta{' '} (opcional) { setRecurringEndDate(nextDate ? toIsoDateLocal(nextDate) : undefined); }} />

Si no se selecciona una fecha, se repetirá por tiempo indefinido.

)} )} Nombre Teléfono Email (opcional)

Se enviará la confirmación de la reserva por email.

{createBookingError &&

{createBookingError}

}
); } function getDayOfWeekLabel(dateIso: string): string { const labels: Record = { MONDAY: 'lunes', TUESDAY: 'martes', WEDNESDAY: 'miércoles', THURSDAY: 'jueves', FRIDAY: 'viernes', SATURDAY: 'sábado', SUNDAY: 'domingo', }; return labels[getDayOfWeek(dateIso)] ?? ''; }