diff --git a/public/images/avatar.png b/public/images/avatar.png new file mode 100644 index 0000000..92e4bd2 Binary files /dev/null and b/public/images/avatar.png differ diff --git a/public/images/icons/heart.svg b/public/images/icons/heart.svg new file mode 100644 index 0000000..bd086a2 --- /dev/null +++ b/public/images/icons/heart.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/icons/pin.svg b/public/images/icons/pin.svg new file mode 100644 index 0000000..3a10091 --- /dev/null +++ b/public/images/icons/pin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/icons/review-star.svg b/public/images/icons/review-star.svg new file mode 100644 index 0000000..63790e7 --- /dev/null +++ b/public/images/icons/review-star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/icons/share.svg b/public/images/icons/share.svg new file mode 100644 index 0000000..26b1876 --- /dev/null +++ b/public/images/icons/share.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/catalog/[id]/components/BookingWidget.tsx b/src/app/catalog/[id]/components/BookingWidget.tsx index 26311c8..a920e38 100644 --- a/src/app/catalog/[id]/components/BookingWidget.tsx +++ b/src/app/catalog/[id]/components/BookingWidget.tsx @@ -4,15 +4,14 @@ 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 [departureDate] = useState(); + const [arrivalDate] = useState(); const [guests, setGuests] = useState({ adults: 1, children: 0 }); const [total] = useState(0); @@ -30,10 +29,13 @@ export function BookingWidget({ price }: BookingWidgetProps) { }; return ( -
+

- от {price} р/час + от {price} ₽{" "} + + /час +

@@ -85,15 +87,12 @@ export function BookingWidget({ price }: BookingWidgetProps) {
- - Итого: - - - {total} Р + Итого: + + {total} ₽
); } - diff --git a/src/app/catalog/[id]/components/ContactInfo.tsx b/src/app/catalog/[id]/components/ContactInfo.tsx index 14a5698..6104b67 100644 --- a/src/app/catalog/[id]/components/ContactInfo.tsx +++ b/src/app/catalog/[id]/components/ContactInfo.tsx @@ -14,64 +14,57 @@ interface ContactInfoProps { }; } -export function ContactInfo({ - contactPerson, - requisites, -}: ContactInfoProps) { +export function ContactInfo({ contactPerson, requisites }: ContactInfoProps) { return ( -
-
-
-
+
+
+
+
{contactPerson.name}
-
-

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

+ {contactPerson.name}

- {contactPerson.name} + Контактное лицо

+
-
-

- Реквизиты -

-
-
- ИП: - - {requisites.ip} - -
-
- ИНН: - - {requisites.inn} - -
-
- - ОГРН/ОГРНИП: - - - {requisites.ogrn} - -
+
+

+ Реквизиты +

+
+
+ ИП + + {requisites.ip} + +
+
+ ИНН + + {requisites.inn} + +
+
+ + ОГРН/ОГРНИП + + + {requisites.ogrn} +
); } - - - diff --git a/src/app/catalog/[id]/components/YachtAvailability.tsx b/src/app/catalog/[id]/components/YachtAvailability.tsx index 8be9f9b..4548260 100644 --- a/src/app/catalog/[id]/components/YachtAvailability.tsx +++ b/src/app/catalog/[id]/components/YachtAvailability.tsx @@ -2,38 +2,24 @@ import { useState } from "react"; import { Calendar } from "@/components/ui/calendar"; -import { format } from "date-fns"; +import { isSameMonth, isBefore, startOfDay, format } from "date-fns"; import { ru } from "date-fns/locale"; -import { ChevronLeft, ChevronRight } from "lucide-react"; -import Icon from "@/components/ui/icon"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; 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 today = startOfDay(new Date()); + const [currentMonth, setCurrentMonth] = useState( + new Date(today.getFullYear(), today.getMonth(), 1) + ); 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) => @@ -43,101 +29,135 @@ export function YachtAvailability({ price }: YachtAvailabilityProps) { ); }; - const handlePreviousMonth = () => { + const isDateInPast = (date: Date) => { + return isBefore(startOfDay(date), today); + }; + + const shouldBeCrossedOut = (date: Date) => { + // Перечеркиваем если день занят или находится до текущего дня + return isDateUnavailable(date) || isDateInPast(date); + }; + + const goToPreviousMonth = () => { setCurrentMonth( - new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1) + new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() - 1, + 1 + ) ); }; - const handleNextMonth = () => { + const goToNextMonth = () => { setCurrentMonth( - new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1) + new Date( + currentMonth.getFullYear(), + currentMonth.getMonth() + 1, + 1 + ) ); }; return ( -
+
-

+

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

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

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

- +
+
+
+ + + {format(currentMonth, "LLLL yyyy", { locale: ru })} + + +
- { - const isAvailable = isDateAvailable(day.date); - const isUnavailable = isDateUnavailable(day.date); - const isDay30 = day.date.getDate() === 30; + // Показываем только дни текущего месяца + if (!isSameMonth(day.date, currentMonth)) { + return
; + } + + const isCrossedOut = shouldBeCrossedOut(day.date); return ( ); }, @@ -147,4 +167,3 @@ export function YachtAvailability({ price }: YachtAvailabilityProps) {
); } - diff --git a/src/app/catalog/[id]/components/YachtCharacteristics.tsx b/src/app/catalog/[id]/components/YachtCharacteristics.tsx index 90f5458..7c2822f 100644 --- a/src/app/catalog/[id]/components/YachtCharacteristics.tsx +++ b/src/app/catalog/[id]/components/YachtCharacteristics.tsx @@ -33,19 +33,19 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) { return (
-

+

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

-
+
{characteristics.map((char, index) => (
- {char.label}: + {char.label} - + {char.value}
@@ -54,6 +54,3 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
); } - - - diff --git a/src/app/catalog/[id]/components/YachtGallery.tsx b/src/app/catalog/[id]/components/YachtGallery.tsx index 16ace2e..0014d38 100644 --- a/src/app/catalog/[id]/components/YachtGallery.tsx +++ b/src/app/catalog/[id]/components/YachtGallery.tsx @@ -52,7 +52,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) { {images.map((img, index) => ( -
+
{`Yacht - {badge && ( -
-
- - {badge} -
-
- )} -
- {index + 1}/{images.length} -
))} @@ -78,6 +67,21 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) { + {/* Badge - поверх слайдера, не скроллится */} + {badge && ( +
+
+ + {badge} +
+
+ )} + {/* Photo counter - поверх слайдера, не скроллится */} +
+
+ {current + 1}/{images.length} +
+
{/* Thumbnails */} @@ -86,7 +90,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) { -
- {/* Main Content Grid */} -
- {/* Left Column - Gallery and Availability */} -
+ {/* Main Content */} +
{/* Gallery */} - {/* Availability */} - + {/* Content with Booking Widget on the right */} +
+ {/* Left column - all content below gallery */} +
+ {/* Availability */} + - {/* Characteristics */} - + {/* Characteristics */} + - {/* Description */} -
-

- Описание -

-

- {yacht.description} -

-
+ {/* Description */} +
+

+ Описание +

+

+ {yacht.description} +

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

- Отзывы -

-

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

+ {/* Reviews */} +
+
+ +

+ Отзывы +

+
+
+

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

+
+
+
+ + {/* Right column - Booking Widget (sticky) */} +
+ +
- - {/* Right Column - Booking Widget */} -
- -
-
diff --git a/src/components/ui/icon.tsx b/src/components/ui/icon.tsx index 16c05b0..047f4e2 100644 --- a/src/components/ui/icon.tsx +++ b/src/components/ui/icon.tsx @@ -22,6 +22,10 @@ import PriceMinIcon from "../../../public/images/icons/price-min.svg"; import PriceMaxIcon from "../../../public/images/icons/price-max.svg"; import BoatYearMinIcon from "../../../public/images/icons/boat-year-min.svg"; import BoatYearMaxIcon from "../../../public/images/icons/boat-year-max.svg"; +import PinIcon from "../../../public/images/icons/pin.svg"; +import HeartIcon from "../../../public/images/icons/heart.svg"; +import ShareIcon from "../../../public/images/icons/share.svg"; +import ReviewStarIcon from "../../../public/images/icons/review-star.svg"; // Объект с иконками для удобного доступа const icons = { @@ -45,6 +49,10 @@ const icons = { priceMax: PriceMaxIcon, boatYearMin: BoatYearMinIcon, boatYearMax: BoatYearMaxIcon, + pin: PinIcon, + heart: HeartIcon, + share: ShareIcon, + reviewStar: ReviewStarIcon, }; export type IconName = @@ -67,7 +75,11 @@ export type IconName = | "priceMin" | "priceMax" | "boatYearMin" - | "boatYearMax"; + | "boatYearMax" + | "pin" + | "heart" + | "share" + | "reviewStar"; export interface IconProps extends Omit, "name" | "preserveAspectRatio"> {