import { useState } from 'react'; import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, isSameMonth, addMonths, subMonths, isSameDay, differenceInDays, parseISO, isWithinInterval } from 'date-fns'; import { es } from 'date-fns/locale/es'; import { ChevronLeft, ChevronRight, Users, Moon, Ban } from 'lucide-react'; import type { Reservation } from '../types'; interface Props { reservations: Reservation[]; onSelectDay: (day: Date) => void; onSelectReservation: (reservation: Reservation) => void; } export function CalendarGrid({ reservations, onSelectDay, onSelectReservation }: Props) { const [currentDate, setCurrentDate] = useState(new Date()); const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(monthStart); const calendarStart = startOfWeek(monthStart, { weekStartsOn: 0 }); const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 0 }); const calendarDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }); // Organizar en semanas const weeks: Date[][] = []; for (let i = 0; i < calendarDays.length; i += 7) { weeks.push(calendarDays.slice(i, i + 7)); } const nextMonth = () => setCurrentDate(addMonths(currentDate, 1)); const prevMonth = () => setCurrentDate(subMonths(currentDate, 1)); // Función para verificar si un día está ocupado const isDayOccupied = (day: Date): boolean => { return reservations.some(res => { const startDate = parseISO(res.start_date); const endDate = parseISO(res.end_date); // Un día está ocupado si está dentro del rango [start_date, end_date] (ambos inclusive) return isWithinInterval(day, { start: startDate, end: endDate }) || isSameDay(day, startDate) || isSameDay(day, endDate); }); }; // Renderizar bloques de reserva const renderReservationBlocks = () => { return reservations.map((res) => { const startDate = parseISO(res.start_date); const endDate = parseISO(res.end_date); const dayIndex = calendarDays.findIndex(day => isSameDay(day, startDate)); if (dayIndex === -1) return null; const weekIndex = Math.floor(dayIndex / 7); const dayOfWeek = dayIndex % 7; const duration = differenceInDays(endDate, startDate) + 1; const nights = duration - 1; const isTeneriffa = res.origin === 'Teneriffa2000'; const gradient = isTeneriffa ? 'from-blue-600/90 via-blue-500/90 to-blue-400/90' : 'from-yellow-600/90 via-yellow-500/90 to-yellow-400/90'; const borderColor = isTeneriffa ? 'border-blue-400' : 'border-yellow-400'; const shadowColor = isTeneriffa ? 'shadow-blue-500/50' : 'shadow-yellow-500/50'; return (
onSelectReservation(res)} className={` absolute cursor-pointer group bg-gradient-to-r ${gradient} ${borderColor} border-l-4 rounded-2xl p-3 hover:scale-105 transition-all duration-300 shadow-2xl ${shadowColor} backdrop-blur-xl z-10 `} style={{ top: `${weekIndex * 100 + 50}px`, left: `${(dayOfWeek * 100 / 7) + 0.75}%`, width: `${Math.min(duration, 7 - dayOfWeek) * (100 / 7) - 1.5}%`, height: '60px' }} >
{res.client_name}
< div className =\"flex items-center gap-1 text-white/90\"> < Moon className =\"w-3 h-3\" /> < span className =\"text-[11px] font-semibold\">{nights}n
< Users className =\"w-3 h-3\" /> < span className =\"text-[11px] font-semibold\">{res.adults_count + res.children_count}p
); }); }; return (
{/* Header */ }
< div >

{ format(currentDate, 'MMMM yyyy', { locale: es }) }

Vista mensual de reservas

< button onClick = { prevMonth } className =\"p-3 rounded-xl bg-gradient-to-br from-slate-700 to-slate-800 hover:from-slate-600 hover:to-slate-700 text-slate-200 transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-110 border border-slate-600/50\" >
{/* Calendar */ } < div className =\"bg-slate-800/30 backdrop-blur-xl rounded-2xl overflow-hidden border border-slate-700/50 shadow-2xl\"> {/* Days header */ }
{ ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'].map((day) => (
{ day }
)) }
{/* Calendar grid */ } < div className =\"relative\"> { weeks.map((week, weekIdx) => (
{ week.map((day) => { const isCurrentMonth = isSameMonth(day, monthStart); const isOccupied = isDayOccupied(day); return (
{ if (!isOccupied) { onSelectDay(day); } }} className={` relative h-28 p-3 border-r border-slate-700/30 last:border-r-0 transition-all duration-300 group ${!isCurrentMonth ? 'bg-slate-900/50' : ''} ${isOccupied ? 'cursor-not-allowed bg-red-900/10' : 'cursor-pointer hover:bg-gradient-to-br hover:from-slate-700/50 hover:to-slate-600/30' } `} > {format(day, 'd')} {/* Indicador visual de día ocupado */} {isOccupied && isCurrentMonth && (
) }
); }) }
))} {/* Reservation blocks */ } { renderReservationBlocks() } {/* Legend */ } < div className =\"mt-6 flex items-center gap-8 text-sm text-slate-400\"> < div className =\"flex items-center gap-3 px-4 py-2 bg-gradient-to-r from-blue-500/10 to-transparent rounded-xl border border-blue-500/20\"> < div className =\"w-4 h-4 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 shadow-lg shadow-blue-500/50\"> < span className =\"font-semibold\">Teneriffa2000
< div className =\"w-4 h-4 rounded-lg bg-gradient-to-br from-yellow-500 to-yellow-600 shadow-lg shadow-yellow-500/50\">
< span className =\"font-semibold\">Naturcalabacera ); }