"use client"; import { useState, useMemo } from "react"; import { Calendar } from "@/components/ui/calendar"; import { isSameMonth, isBefore, startOfDay, format, eachDayOfInterval, startOfMonth, endOfMonth, } from "date-fns"; import { ru } from "date-fns/locale"; import { ChevronLeftIcon, ChevronRightIcon, Clock } from "lucide-react"; interface Reservation { id: number; reservatorId: number; yachtId: number; startUtc: number; endUtc: number; } interface YachtAvailabilityProps { price: string; mobile?: boolean; reservations?: Reservation[]; } export function YachtAvailability({ price, mobile = false, reservations = [], }: YachtAvailabilityProps) { const today = startOfDay(new Date()); const [currentMonth, setCurrentMonth] = useState( new Date(today.getFullYear(), today.getMonth(), 1) ); const [startTime, setStartTime] = useState(""); const [endTime, setEndTime] = useState(""); const unavailableDates = Array.from({ length: 26 }, (_, i) => { return new Date(2025, 3, i + 1); }); // Format time from Unix timestamp to HH:mm in UTC const formatTimeFromUnix = (unixTimestamp: number) => { const date = new Date(unixTimestamp * 1000); // Format in UTC to avoid timezone conversion return format(date, "HH:mm"); }; // Get time portion of a UTC timestamp const getUTCTime = (unixTimestamp: number) => { const date = new Date(unixTimestamp * 1000); return date.getUTCHours() * 60 + date.getUTCMinutes(); // minutes since midnight UTC }; // Get reservations for a specific date with proper time splitting const getReservationsForDate = (date: Date) => { const dayStart = Math.floor(startOfDay(date).getTime() / 1000); const dayEnd = dayStart + 24 * 60 * 60; const dayReservations: Array<{ id: number; startTime: string; endTime: string; }> = []; reservations.forEach((reservation) => { // Check if reservation overlaps with this day if (reservation.startUtc < dayEnd && reservation.endUtc > dayStart) { // Calculate the actual time range for this specific day const dayReservationStart = Math.max(reservation.startUtc, dayStart); const dayReservationEnd = Math.min(reservation.endUtc, dayEnd); // Format times in UTC to avoid timezone issues const startTime = formatTimeFromUnix(dayReservationStart); const endTime = formatTimeFromUnix(dayReservationEnd); dayReservations.push({ id: reservation.id, startTime, endTime, }); } }); return dayReservations; }; // Check if a date has any reservations const hasReservationsOnDate = (date: Date) => { const dayStart = Math.floor(startOfDay(date).getTime() / 1000); const dayEnd = dayStart + 24 * 60 * 60; return reservations.some((reservation) => { return reservation.startUtc < dayEnd && reservation.endUtc > dayStart; }); }; const isDateUnavailable = (date: Date) => { return unavailableDates.some( (d) => d.getDate() === date.getDate() && d.getMonth() === date.getMonth() && d.getFullYear() === date.getFullYear() ); }; const isDateInPast = (date: Date) => { return isBefore(startOfDay(date), today); }; const shouldBeCrossedOut = (date: Date) => { return isDateUnavailable(date) || isDateInPast(date); }; const isDateAvailable = (date: Date) => { return !shouldBeCrossedOut(date) && isSameMonth(date, currentMonth); }; const getAvailableDaysCount = () => { const monthStart = startOfMonth(currentMonth); const monthEnd = endOfMonth(currentMonth); const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd, }); return daysInMonth.filter((day) => isDateAvailable(day)).length; }; const goToPreviousMonth = () => { setCurrentMonth( new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1) ); }; const goToNextMonth = () => { setCurrentMonth( new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1) ); }; // Генерация времени для селекта const timeOptions = Array.from({ length: 24 * 2 }, (_, i) => { const hours = Math.floor(i / 2); const minutes = (i % 2) * 30; const timeString = `${String(hours).padStart(2, "0")}:${String( minutes ).padStart(2, "0")}`; return { value: timeString, label: timeString }; }); // Helper function to render time slots for desktop view const renderTimeSlots = (date: Date) => { const dateReservations = getReservationsForDate(date); if (dateReservations.length === 0) { // No reservations, show free time slot return (
08:00—20:00
); } // Show all reservations for this day return (
{dateReservations.map((res) => (
{res.startTime}—{res.endTime}
))}
); }; if (mobile) { return (
{/* Навигация по месяцам */}
{format(currentMonth, "LLLL", { locale: ru })} Свободных дней: {getAvailableDaysCount()}
{/* Календарь */}
{ const weekdays = ["ВС", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"]; return weekdays[date.getDay()]; }, }} classNames={{ root: "w-full", month: "flex w-full flex-col gap-2", nav: "hidden", month_caption: "hidden", caption_label: "hidden", button_previous: "hidden", button_next: "hidden", table: "w-full border-collapse table-fixed", weekdays: "flex w-full mb-2", weekday: "flex-1 text-[#999999] text-xs font-normal p-2 text-center", week: "flex w-full min-h-[50px]", day: "relative flex-1 min-w-0 flex-shrink-0", }} components={{ DayButton: ({ day, ...props }) => { if (!isSameMonth(day.date, currentMonth)) { return
; } const isCrossedOut = shouldBeCrossedOut(day.date); const hasRes = hasReservationsOnDate(day.date); return ( ); }, }} />
{/* Выбор времени */}
По местному времени яхты
); } return (

Доступность яхты

{format(currentMonth, "LLLL yyyy", { locale: ru })}
{ // Показываем только дни текущего месяца if (!isSameMonth(day.date, currentMonth)) { return
; } const isCrossedOut = shouldBeCrossedOut(day.date); return ( ); }, }} />
); }