import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { ChevronLeft, ChevronRight, Dumbbell, Plus, UserX, Users } from 'lucide-react'; import { useEffect, useRef } from 'react'; import { useBooking } from '../booking-provider'; import type { BookingCourtSchedule, BookingTimelineSegment } from '../booking.types'; import { timeToMinutes } from '../lib/booking-time'; import { BookingStatusLegend } from './booking-status-legend'; const courtInfoWidth = 220; const hourWidth = 104; export function BookingTimeline() { const { schedules, timelineHours, visibleTimeRange, currentTime, isLoading, isError, errorMessage, } = useBooking(); const scrollRef = useRef(null); const prevCurrentTimeRef = useRef(null); const rangeStart = timeToMinutes(visibleTimeRange.start); const rangeEnd = timeToMinutes(visibleTimeRange.end); const totalMinutes = rangeEnd - rangeStart; const gridWidth = Math.max((timelineHours.length - 1) * hourWidth, 760); const timelineMarks = timelineHours.map((hour) => ({ hour, left: ((timeToMinutes(hour) - rangeStart) / totalMinutes) * 100, })); useEffect(() => { if (!currentTime || !scrollRef.current) return; if (currentTime === prevCurrentTimeRef.current) return; prevCurrentTimeRef.current = currentTime; const container = scrollRef.current; const indicatorLeft = courtInfoWidth + ((timeToMinutes(currentTime) - rangeStart) / totalMinutes) * gridWidth; const targetScroll = indicatorLeft - container.clientWidth / 2; const clampedScroll = Math.max( 0, Math.min(targetScroll, container.scrollWidth - container.clientWidth) ); requestAnimationFrame(() => { container.scrollTo({ left: clampedScroll, behavior: 'smooth' }); }); }, [currentTime, rangeStart, totalMinutes, gridWidth]); return (
{isLoading && (
Cargando reservas...
)} {isError && !isLoading && (
{errorMessage}
)} {!isLoading && !isError && schedules.length === 0 && (
No hay canchas para los filtros seleccionados.
)} {!isLoading && !isError && schedules.length > 0 && (
{schedules.map((schedule) => ( ))}
{currentTime && ( )}
)}
); } interface TimelineHeaderProps { timelineMarks: Array<{ hour: string; left: number }>; courtInfoWidth: number; gridWidth: number; } function TimelineHeader({ timelineMarks, courtInfoWidth, gridWidth }: TimelineHeaderProps) { return (
{timelineMarks.map((mark) => (
{mark.hour}
))}
); } interface BookingCourtRowProps { schedule: BookingCourtSchedule; courtInfoWidth: number; gridWidth: number; rangeStart: number; totalMinutes: number; timelineMarks: Array<{ hour: string; left: number }>; } function BookingCourtRow({ schedule, courtInfoWidth, gridWidth, rangeStart, totalMinutes, timelineMarks, }: BookingCourtRowProps) { return (
{timelineMarks.map((mark) => ( ))}
{schedule.segments.map((segment) => ( ))}
); } function BookingCourtInfo({ schedule }: { schedule: BookingCourtSchedule }) { return (

{schedule.court.name}

{schedule.court.sport.name}

{schedule.metrics.free} {schedule.metrics.reserved}
); } interface BookingSlotBlockProps { segment: BookingTimelineSegment; schedule: BookingCourtSchedule; rangeStart: number; totalMinutes: number; } function BookingSlotBlock({ segment, schedule, rangeStart, totalMinutes }: BookingSlotBlockProps) { const { openCreateBooking, updateBookingStatus, openBookingTools } = useBooking(); const left = ((segment.startMinutes - rangeStart) / totalMinutes) * 100; const width = ((segment.endMinutes - segment.startMinutes) / totalMinutes) * 100; const isFree = segment.status === 'free'; return ( ); } interface CurrentTimeIndicatorProps { time: string; courtInfoWidth: number; gridWidth: number; rangeStart: number; totalMinutes: number; } function CurrentTimeIndicator({ time, courtInfoWidth, gridWidth, rangeStart, totalMinutes, }: CurrentTimeIndicatorProps) { const left = courtInfoWidth + ((timeToMinutes(time) - rangeStart) / totalMinutes) * gridWidth; return (
{time}
); }