From d5e466b879d3e107371e0adc3ef2327730d72bfb Mon Sep 17 00:00:00 2001 From: Sergey Bolshakov Date: Fri, 12 Dec 2025 02:19:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=91=D1=80=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20'=D0=AF?= =?UTF-8?q?=D1=85=D1=82=D0=B0'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catalog/[id]/components/BookingWidget.tsx | 99 ++++++++++++ .../catalog/[id]/components/ContactInfo.tsx | 77 +++++++++ .../[id]/components/YachtAvailability.tsx | 150 ++++++++++++++++++ .../[id]/components/YachtCharacteristics.tsx | 59 +++++++ .../catalog/[id]/components/YachtGallery.tsx | 107 +++++++++++++ src/app/catalog/[id]/page.tsx | 147 +++++++++++++++++ src/components/form/guest-picker.tsx | 7 +- 7 files changed, 642 insertions(+), 4 deletions(-) create mode 100644 src/app/catalog/[id]/components/BookingWidget.tsx create mode 100644 src/app/catalog/[id]/components/ContactInfo.tsx create mode 100644 src/app/catalog/[id]/components/YachtAvailability.tsx create mode 100644 src/app/catalog/[id]/components/YachtCharacteristics.tsx create mode 100644 src/app/catalog/[id]/components/YachtGallery.tsx create mode 100644 src/app/catalog/[id]/page.tsx diff --git a/src/app/catalog/[id]/components/BookingWidget.tsx b/src/app/catalog/[id]/components/BookingWidget.tsx new file mode 100644 index 0000000..26311c8 --- /dev/null +++ b/src/app/catalog/[id]/components/BookingWidget.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { DatePicker } from "@/components/ui/date-picker"; +import { GuestPicker } from "@/components/form/guest-picker"; +import Icon from "@/components/ui/icon"; + +interface BookingWidgetProps { + price: string; +} + +export function BookingWidget({ price }: BookingWidgetProps) { + const [departureDate, setDepartureDate] = useState(); + const [arrivalDate, setArrivalDate] = useState(); + const [guests, setGuests] = useState({ adults: 1, children: 0 }); + const [total] = useState(0); + + const handleGuestsChange = (adults: number, children: number) => { + setGuests({ adults, children }); + }; + + const handleBook = () => { + // Логика бронирования + console.log("Booking:", { + departureDate, + arrivalDate, + guests, + }); + }; + + return ( +
+
+

+ от {price} р/час +

+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + +
+
+ + Итого: + + + {total} Р + +
+
+
+ ); +} + diff --git a/src/app/catalog/[id]/components/ContactInfo.tsx b/src/app/catalog/[id]/components/ContactInfo.tsx new file mode 100644 index 0000000..14a5698 --- /dev/null +++ b/src/app/catalog/[id]/components/ContactInfo.tsx @@ -0,0 +1,77 @@ +"use client"; + +import Image from "next/image"; + +interface ContactInfoProps { + contactPerson: { + name: string; + avatar: string; + }; + requisites: { + ip: string; + inn: string; + ogrn: string; + }; +} + +export function ContactInfo({ + contactPerson, + requisites, +}: ContactInfoProps) { + return ( +
+
+
+
+ {contactPerson.name} +
+
+

+ Контактное лицо +

+

+ {contactPerson.name} +

+
+
+ +
+

+ Реквизиты +

+
+
+ ИП: + + {requisites.ip} + +
+
+ ИНН: + + {requisites.inn} + +
+
+ + ОГРН/ОГРНИП: + + + {requisites.ogrn} + +
+
+
+
+
+ ); +} + + + diff --git a/src/app/catalog/[id]/components/YachtAvailability.tsx b/src/app/catalog/[id]/components/YachtAvailability.tsx new file mode 100644 index 0000000..8be9f9b --- /dev/null +++ b/src/app/catalog/[id]/components/YachtAvailability.tsx @@ -0,0 +1,150 @@ +"use client"; + +import { useState } from "react"; +import { Calendar } from "@/components/ui/calendar"; +import { format } from "date-fns"; +import { ru } from "date-fns/locale"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import Icon from "@/components/ui/icon"; + +interface YachtAvailabilityProps { + price: string; +} + +export function YachtAvailability({ price }: YachtAvailabilityProps) { + const [currentMonth, setCurrentMonth] = useState(new Date(2025, 3, 1)); // Апрель 2025 + + // Генерируем доступные даты (27, 28, 29 апреля доступны) + const availableDates = [ + new Date(2025, 3, 27), + new Date(2025, 3, 28), + new Date(2025, 3, 29), + ]; + + const unavailableDates = Array.from({ length: 26 }, (_, i) => { + return new Date(2025, 3, i + 1); + }); + + const isDateAvailable = (date: Date) => { + return availableDates.some( + (d) => + d.getDate() === date.getDate() && + d.getMonth() === date.getMonth() && + d.getFullYear() === date.getFullYear() + ); + }; + + const isDateUnavailable = (date: Date) => { + return unavailableDates.some( + (d) => + d.getDate() === date.getDate() && + d.getMonth() === date.getMonth() && + d.getFullYear() === date.getFullYear() + ); + }; + + const handlePreviousMonth = () => { + setCurrentMonth( + new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1) + ); + }; + + const handleNextMonth = () => { + setCurrentMonth( + new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1) + ); + }; + + return ( +
+
+

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

+
+ + По местному времени яхты +
+
+ +
+
+ +

+ {format(currentMonth, "LLLL yyyy", { locale: ru })} +

+ +
+ + { + const isAvailable = isDateAvailable(day.date); + const isUnavailable = isDateUnavailable(day.date); + const isDay30 = day.date.getDate() === 30; + + return ( + + ); + }, + }} + /> +
+
+ ); +} + diff --git a/src/app/catalog/[id]/components/YachtCharacteristics.tsx b/src/app/catalog/[id]/components/YachtCharacteristics.tsx new file mode 100644 index 0000000..90f5458 --- /dev/null +++ b/src/app/catalog/[id]/components/YachtCharacteristics.tsx @@ -0,0 +1,59 @@ +"use client"; + +interface YachtCharacteristicsProps { + yacht: { + year: number; + maxCapacity: number; + comfortableCapacity: number; + length: number; + width: number; + cabins: number; + material: string; + power: number; + }; +} + +export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) { + const characteristics = [ + { label: "Год", value: yacht.year }, + { + label: "Максимальная вместимость", + value: `${yacht.maxCapacity} человек`, + }, + { + label: "Комфортная вместимость", + value: `${yacht.comfortableCapacity} человек`, + }, + { label: "Длина", value: `${yacht.length} м` }, + { label: "Ширина", value: `${yacht.width} м` }, + { label: "Каюты", value: yacht.cabins }, + { label: "Материал", value: yacht.material }, + { label: "Мощность", value: `${yacht.power} л/с` }, + ]; + + return ( +
+

+ Характеристики +

+
+ {characteristics.map((char, index) => ( +
+ + {char.label}: + + + {char.value} + +
+ ))} +
+
+ ); +} + + + diff --git a/src/app/catalog/[id]/components/YachtGallery.tsx b/src/app/catalog/[id]/components/YachtGallery.tsx new file mode 100644 index 0000000..16ace2e --- /dev/null +++ b/src/app/catalog/[id]/components/YachtGallery.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import Icon from "@/components/ui/icon"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, + type CarouselApi, +} from "@/components/ui/carousel"; + +interface YachtGalleryProps { + images: string[]; + badge?: string; +} + +export function YachtGallery({ images, badge }: YachtGalleryProps) { + const [api, setApi] = useState(); + const [current, setCurrent] = useState(0); + + useEffect(() => { + if (!api) { + return; + } + + setCurrent(api.selectedScrollSnap()); + + api.on("select", () => { + setCurrent(api.selectedScrollSnap()); + }); + }, [api]); + + const scrollTo = (index: number) => { + api?.scrollTo(index); + }; + + return ( +
+ {/* Main Image Carousel */} +
+ + + {images.map((img, index) => ( + +
+ {`Yacht + {badge && ( +
+
+ + {badge} +
+
+ )} +
+ {index + 1}/{images.length} +
+
+
+ ))} +
+ + +
+
+ + {/* Thumbnails */} +
+ {images.map((img, index) => ( + + ))} +
+
+ ); +} + diff --git a/src/app/catalog/[id]/page.tsx b/src/app/catalog/[id]/page.tsx new file mode 100644 index 0000000..d791572 --- /dev/null +++ b/src/app/catalog/[id]/page.tsx @@ -0,0 +1,147 @@ +"use client"; + +import { useParams } from "next/navigation"; +import Link from "next/link"; +import Image from "next/image"; +import Icon from "@/components/ui/icon"; +import { YachtGallery } from "./components/YachtGallery"; +import { YachtAvailability } from "./components/YachtAvailability"; +import { BookingWidget } from "./components/BookingWidget"; +import { YachtCharacteristics } from "./components/YachtCharacteristics"; +import { ContactInfo } from "./components/ContactInfo"; + +export default function YachtDetailPage() { + const params = useParams(); + const id = params.id as string; + + // Данные яхты (в реальном приложении будут загружаться из API) + const yacht = { + id: id, + name: "Яхта Название", + location: "7 Футов", + price: "18 000", + images: [ + "/images/yachts/yacht1.jpg", + "/images/yachts/yacht2.jpg", + "/images/yachts/yacht3.jpg", + "/images/yachts/yacht4.jpg", + "/images/yachts/yacht5.jpg", + "/images/yachts/yacht6.jpg", + ], + badge: "По запросу", + year: 2000, + maxCapacity: 11, + comfortableCapacity: 11, + length: 13, + width: 4, + cabins: 2, + material: "Стеклопластик", + power: 740, + description: "Яхта", + contactPerson: { + name: "Денис", + avatar: "/images/logo.svg", + }, + requisites: { + ip: "Иванов Иван Иванович", + inn: "23000000000", + ogrn: "310000000000001", + }, + }; + + return ( +
+
+ {/* Breadcrumbs */} +
+ + + Аренда яхты + + + > + + + Моторные яхты + + + > + {yacht.name} +
+ + {/* Main Content Container */} +
+ {/* Yacht Title and Actions */} +
+

+ {yacht.name} +

+
+
+ + {yacht.location} +
+ + +
+
+ + {/* Main Content Grid */} +
+ {/* Left Column - Gallery and Availability */} +
+ {/* Gallery */} + + + {/* Availability */} + + + {/* Characteristics */} + + + {/* Description */} +
+

+ Описание +

+

+ {yacht.description} +

+
+ + {/* Contact and Requisites */} + + + {/* Reviews */} +
+

+ Отзывы +

+

+ У этой яхты пока нет отзывов +

+
+
+ + {/* Right Column - Booking Widget */} +
+ +
+
+
+
+
+ ); +} diff --git a/src/components/form/guest-picker.tsx b/src/components/form/guest-picker.tsx index 4c47aac..38d7e6b 100644 --- a/src/components/form/guest-picker.tsx +++ b/src/components/form/guest-picker.tsx @@ -48,11 +48,10 @@ export const GuestPicker: React.FC = ({ const getDisplayText = () => { const total = adults + childrenCount; if (total === 0) return placeholder; + if (total === 1) return "1 гость"; if (childrenCount === 0) - return `${adults} ${adults === 1 ? "взрослый" : "взрослых"}`; - return `${adults} ${ - adults === 1 ? "взрослый" : "взрослых" - }, ${childrenCount} ${childrenCount === 1 ? "ребенок" : "детей"}`; + return `${adults} ${adults === 1 ? "гость" : "гостей"}`; + return `${total} ${total === 1 ? "гость" : "гостей"}`; }; return (