From 7181718b0dbd875643c6d915dc51c0b5f90efa3c Mon Sep 17 00:00:00 2001 From: Sergey Bolshakov Date: Sun, 14 Dec 2025 20:41:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=B1=D1=8D=D0=BA=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.ts | 10 + src/api/types.ts | 18 ++ src/app/catalog/components/CatalogSidebar.tsx | 160 ++++++++++- src/app/components/FeaturedYacht.tsx | 62 ++--- src/app/components/YachtGrid.tsx | 248 ++++++++---------- src/app/confirm/page.tsx | 94 ++++++- src/components/ui/date-picker.tsx | 59 ++++- src/hooks/useApiClient.ts | 2 +- src/lib/utils.ts | 25 ++ 9 files changed, 477 insertions(+), 201 deletions(-) create mode 100644 src/api/types.ts diff --git a/next.config.ts b/next.config.ts index 5f41184..9271192 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,16 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { + protocol: "http", + hostname: "89.169.188.2", + pathname: '/**' + }, + ], + unoptimized: false, + }, webpack(config) { config.module.rules.push({ test: /\.svg$/, diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..3432193 --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,18 @@ +interface CatalogItemDto { + id?: number; + name: string; + length: number; + speed: number; + minCost: number; + mainImageUrl: string; + galleryUrls: string[]; + hasQuickRent: boolean; + isFeatured: boolean; + topText?: string; + isBestOffer?: boolean; +} + +interface MainPageCatalogResponseDto { + featuredYacht: CatalogItemDto; + restYachts: CatalogItemDto[]; + } \ No newline at end of file diff --git a/src/app/catalog/components/CatalogSidebar.tsx b/src/app/catalog/components/CatalogSidebar.tsx index 1486cbe..1d29f87 100644 --- a/src/app/catalog/components/CatalogSidebar.tsx +++ b/src/app/catalog/components/CatalogSidebar.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; import { Slider } from "@/components/ui/slider"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; @@ -15,6 +16,10 @@ interface CatalogSidebarProps { } export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const [lengthRange, setLengthRange] = useState([7, 50]); const [priceRange, setPriceRange] = useState([3000, 200000]); const [yearRange, setYearRange] = useState([1991, 2025]); @@ -24,6 +29,138 @@ export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { const [quickBooking, setQuickBooking] = useState(false); const [hasToilet, setHasToilet] = useState(false); const [vesselType, setVesselType] = useState(""); + const [date, setDate] = useState(null); + const [departureTime, setDepartureTime] = useState("12:00"); + const [arrivalTime, setArrivalTime] = useState("13:00"); + + // Загрузка фильтров из searchParams при монтировании и изменении URL + useEffect(() => { + const lengthMin = searchParams.get("lengthMin"); + const lengthMax = searchParams.get("lengthMax"); + if (lengthMin && lengthMax) { + setLengthRange([parseInt(lengthMin), parseInt(lengthMax)]); + } else { + setLengthRange([7, 50]); + } + + const priceMin = searchParams.get("priceMin"); + const priceMax = searchParams.get("priceMax"); + if (priceMin && priceMax) { + setPriceRange([parseInt(priceMin), parseInt(priceMax)]); + } else { + setPriceRange([3000, 200000]); + } + + const yearMin = searchParams.get("yearMin"); + const yearMax = searchParams.get("yearMax"); + if (yearMin && yearMax) { + setYearRange([parseInt(yearMin), parseInt(yearMax)]); + } else { + setYearRange([1991, 2025]); + } + + const adultsParam = searchParams.get("adults"); + setAdults(adultsParam ? parseInt(adultsParam) : 0); + + const childrenParam = searchParams.get("children"); + setChildren(childrenParam ? parseInt(childrenParam) : 0); + + const paymentTypeParam = searchParams.get("paymentType"); + setPaymentType(paymentTypeParam || "all"); + + const quickBookingParam = searchParams.get("quickBooking"); + setQuickBooking(quickBookingParam === "true"); + + const hasToiletParam = searchParams.get("hasToilet"); + setHasToilet(hasToiletParam === "true"); + + const vesselTypeParam = searchParams.get("vesselType"); + setVesselType(vesselTypeParam || ""); + + const dateParam = searchParams.get("date"); + if (dateParam) { + const parsedDate = new Date(dateParam); + if (!isNaN(parsedDate.getTime())) { + setDate(parsedDate); + } else { + setDate(null); + } + } else { + setDate(null); + } + + const departureTimeParam = searchParams.get("departureTime"); + setDepartureTime(departureTimeParam || "12:00"); + + const arrivalTimeParam = searchParams.get("arrivalTime"); + setArrivalTime(arrivalTimeParam || "13:00"); + }, [searchParams]); + + // Функция для сохранения фильтров в searchParams + const handleApplyFilters = () => { + const params = new URLSearchParams(); + + // Сохраняем только нестандартные значения + if (lengthRange[0] !== 7 || lengthRange[1] !== 50) { + params.set("lengthMin", lengthRange[0].toString()); + params.set("lengthMax", lengthRange[1].toString()); + } + + if (priceRange[0] !== 3000 || priceRange[1] !== 200000) { + params.set("priceMin", priceRange[0].toString()); + params.set("priceMax", priceRange[1].toString()); + } + + if (yearRange[0] !== 1991 || yearRange[1] !== 2025) { + params.set("yearMin", yearRange[0].toString()); + params.set("yearMax", yearRange[1].toString()); + } + + if (adults > 0) { + params.set("adults", adults.toString()); + } + + if (children > 0) { + params.set("children", children.toString()); + } + + if (paymentType !== "all") { + params.set("paymentType", paymentType); + } + + if (quickBooking) { + params.set("quickBooking", "true"); + } + + if (hasToilet) { + params.set("hasToilet", "true"); + } + + if (vesselType) { + params.set("vesselType", vesselType); + } + + if (date) { + params.set("date", date.toISOString()); + } + + if (departureTime !== "12:00") { + params.set("departureTime", departureTime); + } + + if (arrivalTime !== "13:00") { + params.set("arrivalTime", arrivalTime); + } + + // Обновляем URL без прокрутки страницы + const newUrl = params.toString() + ? `${pathname}?${params.toString()}` + : pathname; + router.replace(newUrl, { scroll: false }); + + // Вызываем callback, если он есть + onApply?.(); + }; const formatPrice = (value: number) => { return new Intl.NumberFormat("ru-RU").format(value) + " Р"; @@ -39,6 +176,11 @@ export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { setQuickBooking(false); setHasToilet(false); setVesselType(""); + setDate(null); + setDepartureTime("12:00"); + setArrivalTime("13:00"); + // Очищаем URL параметры без прокрутки страницы + router.replace(pathname, { scroll: false }); }; const activeFiltersCount = [ @@ -50,6 +192,9 @@ export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { quickBooking, hasToilet, vesselType !== "", + date !== null, + departureTime !== "12:00", + arrivalTime !== "13:00", ].filter(Boolean).length; return ( @@ -111,7 +256,16 @@ export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { {/* Дата */}
- + setDate(newDate || null)} + onDepartureTimeChange={setDepartureTime} + onArrivalTimeChange={setArrivalTime} + />
{/* Гостей */} @@ -259,7 +413,7 @@ export default function CatalogSidebar({ onApply }: CatalogSidebarProps) { {/* Кнопка Применить */}