Доработки по главной странице, итерация 12

This commit is contained in:
Sergey Bolshakov 2025-10-27 22:47:53 +03:00
parent e420440cea
commit 2b7c336239
4 changed files with 317 additions and 14 deletions

View File

@ -2,7 +2,6 @@
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DatePicker } from "@/components/ui/date-picker";
import { import {
Carousel, Carousel,
CarouselContent, CarouselContent,
@ -13,7 +12,7 @@ import {
import Image from "next/image"; import Image from "next/image";
import Icon from "@/components/ui/icon"; import Icon from "@/components/ui/icon";
import { useState } from "react"; import { useState } from "react";
import { GuestPicker } from "@/components/form/guest-picker"; import { GuestDatePicker } from "@/components/form/guest-date-picker";
const yacht = { const yacht = {
name: "Яхта", name: "Яхта",
@ -140,7 +139,7 @@ export default function FeaturedYacht() {
{/* Promoted badge */} {/* Promoted badge */}
{yacht.isPromoted && ( {yacht.isPromoted && (
<div className="flex items-center gap-2 text-sm text-gray-400"> <div className="flex items-center gap-2 text-sm text-gray-400">
<Icon size={21} name="ad" /> <Icon className="min-w-[21px] min-h-[21px]" size={21} name="ad" />
<span> <span>
Это объявление продвигается.{" "} Это объявление продвигается.{" "}
<span className="underline cursor-pointer"> <span className="underline cursor-pointer">
@ -152,7 +151,7 @@ export default function FeaturedYacht() {
</div> </div>
{/* Right side - Booking form */} {/* Right side - Booking form */}
<div className="min-w-[296px] flex flex-col justify-between"> <div className="min-w-[296px] flex-0 flex flex-col justify-between">
<div> <div>
{/* Promoted banner - Desktop only */} {/* Promoted banner - Desktop only */}
<div <div
@ -182,16 +181,11 @@ export default function FeaturedYacht() {
</div> </div>
{/* Booking form */} {/* Booking form */}
<div className="space-y-5 mb-8"> <div className="mb-8">
<div> <div>
<DatePicker showIcon={false} /> <GuestDatePicker />
</div>
<div>
<DatePicker showIcon={false} />
</div>
<div>
<GuestPicker showIcon={false} />
</div> </div>
</div> </div>
{/* Book button */} {/* Book button */}

View File

@ -0,0 +1,310 @@
"use client";
import React, { useState } from "react";
import { format } from "date-fns";
import { ru } from "date-fns/locale";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Counter } from "@/components/ui/counter";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
interface GuestDatePickerProps {
onApply?: (data: {
date: Date | undefined;
departureTime: string;
arrivalTime: string;
adults: number;
children: number;
}) => void;
className?: string;
}
interface CommonPopoverContentProps {
date: Date | undefined;
setDate: (date: Date | undefined) => void;
departureTime: string;
setDepartureTime: (time: string) => void;
arrivalTime: string;
setArrivalTime: (time: string) => void;
adults: number;
setAdults: (count: number) => void;
childrenCount: number;
setChildrenCount: (count: number) => void;
handleApply: () => void;
}
const CommonPopoverContent: React.FC<CommonPopoverContentProps> = ({
date,
setDate,
departureTime,
setDepartureTime,
arrivalTime,
setArrivalTime,
adults,
setAdults,
childrenCount,
setChildrenCount,
handleApply,
}) => {
return (
<PopoverContent className="rounded-[20px] p-6 pb-4 w-[374px]">
{/* Календарь */}
<div className="min-h-fit">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="mb-[24px]"
locale={ru}
disabled={(date) =>
date < new Date(new Date().setHours(0, 0, 0, 0))
}
classNames={{
root: "w-full",
month: "flex w-full flex-col gap-4",
button_previous:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
button_next:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
month_caption:
"flex h-8 w-full items-center justify-center px-8 text-gray-700 font-semibold",
table: "w-full border-collapse",
weekdays: "flex",
weekday:
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
day_button: "font-bold ring-0 focus:ring-0",
week: "mt-2 flex w-full",
today: "bg-gray-100 text-gray-900 rounded-full",
outside: "text-gray-300",
disabled: "text-gray-400 cursor-not-allowed",
selected:
"rounded-full border-none outline-none !bg-brand text-white",
}}
/>
</div>
{/* Счетчики гостей */}
<div className="mb-[24px] flex gap-3">
<Counter
label="Взрослые"
value={adults}
onChange={setAdults}
min={0}
max={10}
/>
<Counter
label="Дети"
value={childrenCount}
onChange={setChildrenCount}
min={0}
max={10}
/>
</div>
{/* Поля времени */}
<div className="flex gap-3 mb-6">
<div className="relative w-full h-12 px-3 border border-gray-300 rounded-full text-gray-700 font-medium text-center">
<label className="absolute left-[24px] top-0 transform -translate-y-1/2 text-xs text-gray-500 pointer-events-none transition-all duration-200 bg-white px-1">
Выход
</label>
<div className="relative h-full flex align-center">
<ChevronDownIcon className="absolute right-0 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
<input
type="time"
value={departureTime}
onChange={(e) => setDepartureTime(e.target.value)}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
<div className="relative w-full h-12 px-3 border border-gray-300 rounded-full text-gray-700 font-medium text-center">
<label className="absolute left-[24px] top-0 transform -translate-y-1/2 text-xs text-gray-500 pointer-events-none transition-all duration-200 bg-white px-1">
Заход
</label>
<div className="relative h-full flex align-center">
<ChevronDownIcon className="absolute right-0 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
<input
type="time"
value={arrivalTime}
onChange={(e) => setArrivalTime(e.target.value)}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
</div>
{/* Кнопка Применить */}
<Button
onClick={handleApply}
variant="gradient"
className="font-bold text-white h-[44px] w-full px-8"
>
Применить
</Button>
</PopoverContent>
);
};
export const GuestDatePicker: React.FC<GuestDatePickerProps> = ({
onApply,
className,
}) => {
const [date, setDate] = useState<Date>();
const [departureTime, setDepartureTime] = useState("12:00");
const [arrivalTime, setArrivalTime] = useState("13:00");
const [adults, setAdults] = useState(1);
const [children, setChildren] = useState(0);
const [isDepartureOpen, setIsDepartureOpen] = useState(false);
const [isArrivalOpen, setIsArrivalOpen] = useState(false);
const [isGuestOpen, setIsGuestOpen] = useState(false);
const handleApply = () => {
onApply?.({
date,
departureTime,
arrivalTime,
adults,
children,
});
setIsDepartureOpen(false);
setIsArrivalOpen(false);
setIsGuestOpen(false);
};
const getDepartureDisplayText = () => {
if (!date || !departureTime) return "Выход";
return (
<>
{format(date, "d MMMM", {
locale: ru,
})}
, <span className="font-bold">{departureTime}</span>
</>
);
};
const getArrivalDisplayText = () => {
if (!date || !arrivalTime) return "Заход";
return (
<>
{format(date, "d MMMM", {
locale: ru,
})}
, <span className="font-bold">{arrivalTime}</span>
</>
);
};
const getGuestDisplayText = () => {
if (adults === 1 && children === 0) return "1 гость";
return (
<span className="font-bold">
Взрослых: {adults}, Детей: {children}
</span>
);
};
return (
<div className={className}>
<div className="space-y-5">
{/* Кнопка Выход */}
<Popover
open={isDepartureOpen}
onOpenChange={setIsDepartureOpen}
>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-[64px] px-4 w-full justify-between font-normal"
>
<div className="flex items-center">
<span>{getDepartureDisplayText()}</span>
</div>
</Button>
</PopoverTrigger>
<CommonPopoverContent
date={date}
setDate={setDate}
departureTime={departureTime}
setDepartureTime={setDepartureTime}
arrivalTime={arrivalTime}
setArrivalTime={setArrivalTime}
adults={adults}
setAdults={setAdults}
childrenCount={children}
setChildrenCount={setChildren}
handleApply={handleApply}
/>
</Popover>
{/* Кнопка Заход */}
<Popover open={isArrivalOpen} onOpenChange={setIsArrivalOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-[64px] px-4 w-full justify-between font-normal"
>
<div className="flex items-center">
<span>{getArrivalDisplayText()}</span>
</div>
</Button>
</PopoverTrigger>
<CommonPopoverContent
date={date}
setDate={setDate}
departureTime={departureTime}
setDepartureTime={setDepartureTime}
arrivalTime={arrivalTime}
setArrivalTime={setArrivalTime}
adults={adults}
setAdults={setAdults}
childrenCount={children}
setChildrenCount={setChildren}
handleApply={handleApply}
/>
</Popover>
{/* Кнопка Гости */}
<Popover open={isGuestOpen} onOpenChange={setIsGuestOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-[64px] px-4 w-full justify-between font-normal"
>
<div className="flex items-center">
<span>{getGuestDisplayText()}</span>
</div>
{isGuestOpen ? (
<ChevronUpIcon className="h-4 w-4" />
) : (
<ChevronDownIcon className="h-4 w-4" />
)}
</Button>
</PopoverTrigger>
<CommonPopoverContent
date={date}
setDate={setDate}
departureTime={departureTime}
setDepartureTime={setDepartureTime}
arrivalTime={arrivalTime}
setArrivalTime={setArrivalTime}
adults={adults}
setAdults={setAdults}
childrenCount={children}
setChildrenCount={setChildren}
handleApply={handleApply}
/>
</Popover>
</div>
</div>
);
};

View File

@ -31,7 +31,7 @@ export const Counter: React.FC<CounterProps> = ({
}; };
return ( return (
<div className="relative flex w-full items-center justify-between items-center border border-gray-200 rounded-full px-8 py-3 bg-white"> <div className="relative flex w-full items-center justify-between items-center border border-gray-200 rounded-full px-4 py-3 bg-white">
<label className="absolute left-[32px] top-0 transform -translate-y-1/2 text-xs text-gray-500 transition-all duration-200 bg-white px-1"> <label className="absolute left-[32px] top-0 transform -translate-y-1/2 text-xs text-gray-500 transition-all duration-200 bg-white px-1">
{label} {label}
</label> </label>

View File

@ -1 +0,0 @@