Add all possible bullshit

This commit is contained in:
Иван 2025-12-15 00:59:03 +03:00
parent d177eee970
commit 745d58ab3a
14 changed files with 2241 additions and 2097 deletions

View File

@ -1,4 +1,4 @@
interface CatalogItemDto {
interface CatalogItemShortDto {
id?: number;
name: string;
length: number;
@ -13,11 +13,65 @@ interface CatalogItemDto {
}
interface MainPageCatalogResponseDto {
featuredYacht: CatalogItemDto;
restYachts: CatalogItemDto[];
featuredYacht: CatalogItemShortDto;
restYachts: CatalogItemShortDto[];
}
interface CatalogFilteredResponseDto {
items: CatalogItemDto[];
items: CatalogItemShortDto[];
total: number;
}
interface Reservation {
id: number;
yachtId: number;
reservatorId: number;
startUtc: number;
endUtc: number;
}
interface Review {
id: number;
reviewerId: number;
yachtId: number;
starsCount: number;
description: string;
}
interface User {
userId?: number;
firstName?: string;
lastName?: string;
phone?: string;
email?: string;
password?: string;
yachts?: Yacht[];
companyName?: string;
inn?: number;
ogrn?: number;
}
interface CatalogItemLongDto extends CatalogItemShortDto {
year: number;
comfortCapacity: number;
maxCapacity: number;
width: number;
cabinsCount: number;
matherial: string;
power: number;
description: string;
owner: User;
reviews: Review[];
reservations: Reservation[];
}
interface Yacht {
yachtId: number;
name: string;
model: string;
year: number;
length: number;
userId: number;
createdAt: Date;
updatedAt: Date;
}

View File

@ -5,15 +5,17 @@ import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { DatePicker } from "@/components/ui/date-picker";
import { GuestPicker } from "@/components/form/guest-picker";
import { format } from "date-fns";
interface BookingWidgetProps {
price: string;
yacht: CatalogItemLongDto;
}
export function BookingWidget({ price }: BookingWidgetProps) {
export function BookingWidget({ price, yacht }: BookingWidgetProps) {
const router = useRouter();
const [departureDate] = useState<Date | undefined>();
const [arrivalDate] = useState<Date | undefined>();
const [departureDate, setDepartureDate] = useState<Date | undefined>();
const [arrivalDate, setArrivalDate] = useState<Date | undefined>();
const [guests, setGuests] = useState({ adults: 1, children: 0 });
const [total] = useState(0);
@ -22,13 +24,18 @@ export function BookingWidget({ price }: BookingWidgetProps) {
};
const handleBook = () => {
// Логика бронирования
console.log("Booking:", {
departureDate,
arrivalDate,
guests,
if (!departureDate || !arrivalDate || !yacht || !yacht.id) return;
const params = new URLSearchParams({
yachtId: yacht.id.toString(),
departureDate: format(departureDate, "yyyy-MM-dd"),
departureTime: format(departureDate, "HH:mm"),
arrivalDate: format(arrivalDate, "yyyy-MM-dd"),
arrivalTime: format(arrivalDate, "HH:mm"),
guests: (guests.adults + guests.children).toString(),
});
router.push("/confirm");
router.push(`/confirm?${params.toString()}`);
};
return (
@ -36,9 +43,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
<div className="mb-6">
<p className="text-2xl font-bold text-[#333333] mb-2">
от {price} {" "}
<span className="text-base font-normal text-[#999999]">
/час
</span>
<span className="text-base font-normal text-[#999999]">/час</span>
</p>
</div>
@ -51,6 +56,9 @@ export function BookingWidget({ price }: BookingWidgetProps) {
variant="small"
placeholder="Выберите дату и время"
showIcon={false}
onDateChange={setDepartureDate}
value={departureDate}
onlyDeparture
/>
</div>
@ -62,6 +70,9 @@ export function BookingWidget({ price }: BookingWidgetProps) {
variant="small"
placeholder="Выберите дату и время"
showIcon={false}
onDateChange={setArrivalDate}
value={arrivalDate}
onlyArrival
/>
</div>
@ -84,6 +95,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
onClick={handleBook}
variant="gradient"
className="w-full h-12 font-bold text-white mb-4"
disabled={!departureDate || !arrivalDate}
>
Забронировать
</Button>
@ -91,9 +103,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
<div className="pt-4 border-t border-gray-200">
<div className="flex justify-between items-center">
<span className="text-base text-[#333333]">Итого:</span>
<span className="text-base font-bold text-[#333333]">
{total}
</span>
<span className="text-base font-bold text-[#333333]">{total} </span>
</div>
</div>
</div>

View File

@ -2,19 +2,7 @@
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) {
export function ContactInfo({ firstName, companyName, inn, ogrn }: User) {
return (
<div className="flex flex-col sm:flex-row gap-5">
<div className="flex-1 rounded-[24px] px-6 py-5 bg-[#f4f4f4]">
@ -22,46 +10,32 @@ export function ContactInfo({ contactPerson, requisites }: ContactInfoProps) {
<div className="relative rounded-full overflow-hidden bg-gray-200 flex items-center justify-center">
<Image
src="/images/avatar.png"
alt={contactPerson.name}
alt={firstName || "avatar"}
width={124}
height={124}
/>
</div>
<div className="flex flex-col justify-between h-full">
<h3 className="text-base font-bold text-[#333333]">
{contactPerson.name}
</h3>
<p className="text-base text-[#333333]">
Контактное лицо
</p>
<h3 className="text-base font-bold text-[#333333]">{firstName}</h3>
<p className="text-base text-[#333333]">Контактное лицо</p>
</div>
</div>
</div>
<div className="flex-1 rounded-[24px] px-6 py-5 bg-[#f4f4f4]">
<h3 className="text-base font-bold text-[#333333] mb-3">
Реквизиты
</h3>
<h3 className="text-base font-bold text-[#333333] mb-3">Реквизиты</h3>
<div className="space-y-2">
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
<span className="text-base text-[#333333]">ИП</span>
<span className="text-base text-[#999999]">
{requisites.ip}
</span>
<span className="text-base text-[#999999]">{companyName}</span>
</div>
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
<span className="text-base text-[#333333]">ИНН</span>
<span className="text-base text-[#999999]">
{requisites.inn}
</span>
<span className="text-base text-[#999999]">{inn}</span>
</div>
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
<span className="text-base text-[#333333]">
ОГРН/ОГРНИП
</span>
<span className="text-base text-[#999999]">
{requisites.ogrn}
</span>
<span className="text-base text-[#333333]">ОГРН/ОГРНИП</span>
<span className="text-base text-[#999999]">{ogrn}</span>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useState, useMemo } from "react";
import { Calendar } from "@/components/ui/calendar";
import {
isSameMonth,
@ -14,14 +14,24 @@ import {
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(
@ -34,6 +44,62 @@ export function YachtAvailability({
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) =>
@ -48,7 +114,6 @@ export function YachtAvailability({
};
const shouldBeCrossedOut = (date: Date) => {
// Перечеркиваем если день занят или находится до текущего дня
return isDateUnavailable(date) || isDateInPast(date);
};
@ -88,6 +153,36 @@ export function YachtAvailability({
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 (
<div className="flex flex-col gap-1.5 w-full mt-1">
<div className="w-fit bg-[#F6BD4D] text-white text-[10px] font-medium px-1 py-0 rounded-full inline-block">
08:0020:00
</div>
</div>
);
}
// Show all reservations for this day
return (
<div className="flex flex-col gap-1 w-full mt-1">
{dateReservations.map((res) => (
<div
key={`${res.id}-${res.startTime}`}
className="w-fit bg-[#2F5CD0] text-white text-[10px] font-medium px-1 py-0 rounded-full inline-block"
>
{res.startTime}{res.endTime}
</div>
))}
</div>
);
};
if (mobile) {
return (
<div className="w-full">
@ -126,15 +221,7 @@ export function YachtAvailability({
locale={ru}
formatters={{
formatWeekdayName: (date) => {
const weekdays = [
"ВС",
"ПН",
"ВТ",
"СР",
"ЧТ",
"ПТ",
"СБ",
];
const weekdays = ["ВС", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"];
return weekdays[date.getDay()];
},
}}
@ -159,9 +246,8 @@ export function YachtAvailability({
return <div className="hidden" />;
}
const isCrossedOut = shouldBeCrossedOut(
day.date
);
const isCrossedOut = shouldBeCrossedOut(day.date);
const hasRes = hasReservationsOnDate(day.date);
return (
<button
@ -180,6 +266,9 @@ export function YachtAvailability({
disabled={isCrossedOut}
>
{day.date.getDate()}
{hasRes && !isCrossedOut && (
<div className="absolute bottom-1 right-1 w-1.5 h-1.5 bg-[#2F5CD0] rounded-full"></div>
)}
</button>
);
},
@ -231,9 +320,7 @@ export function YachtAvailability({
return (
<div className="space-y-4 w-full">
<div className="flex items-center justify-between">
<h2 className="text-base font-bold text-[#333333]">
Доступность яхты
</h2>
<h2 className="text-base font-bold text-[#333333]">Доступность яхты</h2>
</div>
<div className="bg-white w-full">
@ -273,8 +360,7 @@ export function YachtAvailability({
button_next: "hidden",
table: "w-full border-collapse",
weekdays: "hidden",
weekday:
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
weekday: "flex-1 text-gray-500 text-xs font-normal p-2 text-center",
week: "flex w-full",
day: "relative flex-1",
}}
@ -311,17 +397,16 @@ export function YachtAvailability({
{/* Дата и "Доступно:" в одной строке */}
<div className="flex items-center justify-between w-full">
<span className="text-xs text-gray-400">
Доступно:
{hasReservationsOnDate(day.date)
? "Бронь:"
: "Доступно:"}
</span>
<span className="text-sm font-medium text-[#333333]">
{day.date.getDate()}
</span>
</div>
<div className="flex flex-col gap-1.5 w-full mt-1">
<div className="w-fit bg-[#F6BD4D] text-white text-[10px] font-medium px-1 py-0 rounded-full inline-block">
08:0020:00
</div>
</div>
{/* Time slots - reservations first */}
{renderTimeSlots(day.date)}
{/* Цена в нижнем правом углу */}
<span className="absolute bottom-[2px] right-[4px] text-xs text-[#333333] font-medium">
{price} / час

View File

@ -1,16 +1,7 @@
"use client";
interface YachtCharacteristicsProps {
yacht: {
year: number;
maxCapacity: number;
comfortableCapacity: number;
length: number;
width: number;
cabins: number;
material: string;
power: number;
};
yacht: CatalogItemLongDto;
}
export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
@ -22,12 +13,12 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
},
{
label: "Комфортная вместимость",
value: `${yacht.comfortableCapacity} человек`,
value: `${yacht.comfortCapacity} человек`,
},
{ label: "Длина", value: `${yacht.length} м` },
{ label: "Ширина", value: `${yacht.width} м` },
{ label: "Каюты", value: yacht.cabins },
{ label: "Материал", value: yacht.material },
{ label: "Каюты", value: yacht.cabinsCount },
{ label: "Материал", value: yacht.matherial },
{ label: "Мощность", value: `${yacht.power} л/с` },
];
@ -42,9 +33,7 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
key={index}
className="flex justify-between items-center py-4 border-b border-gray-200"
>
<span className="text-base text-[#999999]">
{char.label}
</span>
<span className="text-base text-[#999999]">{char.label}</span>
<span className="text-base font-regular text-[#333333]">
{char.value}
</span>

View File

@ -11,6 +11,7 @@ import {
CarouselPrevious,
type CarouselApi,
} from "@/components/ui/carousel";
import { getImageUrl } from "@/lib/utils";
interface YachtGalleryProps {
images: string[];
@ -54,7 +55,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) {
<CarouselItem key={index}>
<div className="relative w-full h-[60vh] lg:h-[592px] rounded-0 lg:rounded-[24px] overflow-hidden">
<Image
src={img}
src={getImageUrl(img)}
alt={`Yacht image ${index + 1}`}
fill
className="object-cover"
@ -97,7 +98,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) {
}`}
>
<Image
src={img}
src={getImageUrl(img)}
alt={`Thumbnail ${index + 1}`}
fill
className="object-cover"

View File

@ -1,37 +0,0 @@
export const YACHT = {
id: 1,
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",
},
};

View File

@ -1,8 +1,8 @@
"use client";
import { useRouter } from "next/navigation";
import { useRouter, useParams } from "next/navigation";
import Link from "next/link";
import { useState } from "react";
import { useState, useEffect } from "react";
import { ArrowLeft, Heart } from "lucide-react";
import Icon from "@/components/ui/icon";
import { YachtGallery } from "./components/YachtGallery";
@ -10,9 +10,23 @@ import { YachtAvailability } from "./components/YachtAvailability";
import { BookingWidget } from "./components/BookingWidget";
import { YachtCharacteristics } from "./components/YachtCharacteristics";
import { ContactInfo } from "./components/ContactInfo";
import { YACHT } from "./const";
import useApiClient from "@/hooks/useApiClient";
import { formatSpeed } from "@/lib/utils";
export default function YachtDetailPage() {
const { id } = useParams();
const [yacht, setYacht] = useState<CatalogItemLongDto | null>(null);
const client = useApiClient();
useEffect(() => {
(async () => {
const response = await client.get<CatalogItemLongDto>(`/catalog/${id}/`);
setYacht(response.data);
})();
}, [id]);
// const params = useParams();
const router = useRouter();
const [activeTab, setActiveTab] = useState<
@ -24,6 +38,10 @@ export default function YachtDetailPage() {
| "reviews"
>("availability");
if (!yacht) {
return <div />;
}
return (
<main className="bg-[#f4f4f4] min-h-screen ">
{/* Мобильная фиксированная верхняя панель навигации */}
@ -35,9 +53,7 @@ export default function YachtDetailPage() {
>
<ArrowLeft size={24} className="text-[#333333]" />
</button>
<h2 className="text-base font-medium text-[#333333]">
Яхта
</h2>
<h2 className="text-base font-medium text-[#333333]">Яхта</h2>
<button className="flex items-center justify-center">
<Heart size={24} className="text-[#333333]" />
</button>
@ -59,7 +75,7 @@ export default function YachtDetailPage() {
</span>
</Link>
<span>&gt;</span>
<span className="text-[#333333]">{YACHT.name}</span>
<span className="text-[#333333]">{yacht.name}</span>
</div>
</div>
@ -70,14 +86,14 @@ export default function YachtDetailPage() {
<div className="lg:hidden pt-[50px]">
{/* Gallery */}
<YachtGallery
images={YACHT.images}
badge={YACHT.badge}
images={yacht.galleryUrls || []}
badge={!yacht.hasQuickRent ? "По запросу" : ""}
/>
{/* Yacht Title */}
<div className="px-4 pt-4">
<h1 className="text-xl font-bold text-[#333333] mb-4">
{YACHT.name}
{yacht.name}
</h1>
</div>
@ -105,9 +121,7 @@ export default function YachtDetailPage() {
Описание
</button>
<button
onClick={() =>
setActiveTab("characteristics")
}
onClick={() => setActiveTab("characteristics")}
className={`pb-3 text-base font-medium transition-colors whitespace-nowrap ${
activeTab === "characteristics"
? "text-[#008299] border-b-2 border-[#008299]"
@ -143,26 +157,22 @@ export default function YachtDetailPage() {
<div className="px-4 py-6">
{activeTab === "availability" && (
<YachtAvailability
price={YACHT.price}
price={String(yacht.minCost)}
mobile={true}
reservations={yacht.reservations}
/>
)}
{activeTab === "description" && (
<div>
<p className="text-base text-[#666666] leading-relaxed">
{YACHT.description}
{yacht.description}
</p>
</div>
)}
{activeTab === "characteristics" && (
<YachtCharacteristics yacht={YACHT} />
)}
{activeTab === "contact" && (
<ContactInfo
contactPerson={YACHT.contactPerson}
requisites={YACHT.requisites}
/>
<YachtCharacteristics yacht={yacht} />
)}
{activeTab === "contact" && <ContactInfo {...yacht.owner} />}
{activeTab === "reviews" && (
<div>
<div className="flex items-center gap-2 mb-4">
@ -186,20 +196,16 @@ export default function YachtDetailPage() {
{/* Yacht Title and Actions */}
<div className="mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<h1 className="text-xl md:text-2xl font-bold text-[#333333]">
{YACHT.name}
{yacht.name}
</h1>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 text-[#333333]">
<Icon name="pin" size={32} />
<span className="text-base">
{YACHT.location}
</span>
<span className="text-base">{formatSpeed(yacht.speed)}</span>
</div>
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
<Icon name="share" size={32} />
<span className="text-base">
Поделиться
</span>
<span className="text-base">Поделиться</span>
</button>
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
<Icon name="heart" size={32} />
@ -212,8 +218,8 @@ export default function YachtDetailPage() {
<div className="space-y-6">
{/* Gallery */}
<YachtGallery
images={YACHT.images}
badge={YACHT.badge}
images={yacht.galleryUrls || []}
badge={!yacht.hasQuickRent ? "По запросу" : ""}
/>
{/* Content with Booking Widget on the right */}
@ -221,10 +227,13 @@ export default function YachtDetailPage() {
{/* Left column - all content below gallery */}
<div className="flex-1 space-y-6">
{/* Availability */}
<YachtAvailability price={YACHT.price} />
<YachtAvailability
price={String(yacht.minCost)}
reservations={yacht.reservations}
/>
{/* Characteristics */}
<YachtCharacteristics yacht={YACHT} />
<YachtCharacteristics yacht={yacht} />
{/* Description */}
<div>
@ -232,15 +241,12 @@ export default function YachtDetailPage() {
Описание
</h2>
<p className="text-base text-[#666666] leading-relaxed">
{YACHT.description}
{yacht.description}
</p>
</div>
{/* Contact and Requisites */}
<ContactInfo
contactPerson={YACHT.contactPerson}
requisites={YACHT.requisites}
/>
<ContactInfo {...yacht.owner} />
{/* Reviews */}
<div>
@ -260,7 +266,7 @@ export default function YachtDetailPage() {
{/* Right column - Booking Widget (sticky) */}
<div className="lg:w-74 flex-shrink-0 lg:sticky lg:top-24 self-start">
<BookingWidget price={YACHT.price} />
<BookingWidget price={String(yacht.minCost)} yacht={yacht} />
</div>
</div>
</div>
@ -273,14 +279,12 @@ export default function YachtDetailPage() {
<div className="flex items-center justify-between">
<div>
<span className="text-lg font-bold text-[#333333]">
{YACHT.price}
</span>
<span className="text-sm text-[#999999] ml-1">
/ час
{yacht.minCost}
</span>
<span className="text-sm text-[#999999] ml-1">/ час</span>
</div>
<button
onClick={() => router.push("/confirm")}
onClick={() => router.push(`/confirm?yachtId=${yacht.id}`)}
className="bg-[#008299] text-white px-6 py-3 rounded-lg font-bold text-base hover:bg-[#006d7a] transition-colors"
>
Забронировать

View File

@ -15,7 +15,11 @@ import { useState } from "react";
import { GuestDatePicker } from "@/components/form/guest-date-picker";
import { formatMinCost, formatWidth, getImageUrl } from "@/lib/utils";
export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
export default function FeaturedYacht({
yacht,
}: {
yacht: CatalogItemShortDto;
}) {
const [selectedImage, setSelectedImage] = useState(yacht.mainImageUrl);
const handleThumbnailClick = (imageSrc: string) => {
@ -33,8 +37,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
<div
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative lg:hidden"
style={{
backgroundImage:
"url(/images/badge-bg.jpg)",
backgroundImage: "url(/images/badge-bg.jpg)",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
@ -47,14 +50,10 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
{/* Header with yacht name and length */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-3xl font-bold">
{yacht.name}
</h2>
<h2 className="text-3xl font-bold">{yacht.name}</h2>
<div className="flex items-center gap-2 text-gray-600">
<Icon size={16} name="width" />
<span className="text-lg">
{formatWidth(yacht.length)}
</span>
<span className="text-lg">{formatWidth(yacht.length)}</span>
</div>
</div>
@ -89,20 +88,15 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
<div className="relative">
<Image
src={getImageUrl(thumb)}
alt={`${yacht.name
} view ${idx + 1}`}
alt={`${yacht.name} view ${idx + 1}`}
width={80}
height={60}
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${selectedImage ===
thumb
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${
selectedImage === thumb
? "border-[#008299]"
: "border-gray-200 hover:border-gray-400"
}`}
onClick={() =>
handleThumbnailClick(
thumb
)
}
onClick={() => handleThumbnailClick(thumb)}
unoptimized
/>
</div>
@ -139,8 +133,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
<div
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative hidden lg:flex"
style={{
backgroundImage:
"url(/images/badge-bg.jpg)",
backgroundImage: "url(/images/badge-bg.jpg)",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
@ -177,9 +170,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
{/* Total price */}
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
<span className="font-normal">
Итого:
</span>
<span className="font-normal">Итого:</span>
<span>0 </span>
</div>
</div>

View File

@ -8,21 +8,31 @@ import Link from "next/link";
import FeaturedYacht from "./FeaturedYacht";
import useApiClient from "@/hooks/useApiClient";
import { useEffect, useState } from "react";
import { formatMinCost, formatSpeed, formatWidth, getImageUrl } from "@/lib/utils";
import {
formatMinCost,
formatSpeed,
formatWidth,
getImageUrl,
} from "@/lib/utils";
export default function YachtGrid() {
const client = useApiClient();
const [featuredYacht, setFeaturedYacht] = useState<CatalogItemDto | null>(null);
const [yachtCatalog, setYachtCatalog] = useState<CatalogItemDto[] | null>(null);
const [featuredYacht, setFeaturedYacht] =
useState<CatalogItemShortDto | null>(null);
const [yachtCatalog, setYachtCatalog] = useState<
CatalogItemShortDto[] | null
>(null);
useEffect(() => {
(async () => {
const response = await client.get<MainPageCatalogResponseDto>("/catalog/main-page/");
const response = await client.get<MainPageCatalogResponseDto>(
"/catalog/main-page/"
);
setFeaturedYacht(response.data.featuredYacht);
setYachtCatalog(response.data.restYachts);
})();
}, [])
}, []);
return (
<section className="text-white">
@ -39,15 +49,13 @@ export default function YachtGrid() {
Каталог лучших яхт Балаклавы разных ценовых сегментах.
</p>
<p className="text-gray-700 leading-relaxed">
Проверенные лодки с лицензией на перевозки, опытные
капитаны. Выбирайте удобную дату, время и бронируйте.
Проверенные лодки с лицензией на перевозки, опытные капитаны.
Выбирайте удобную дату, время и бронируйте.
</p>
</div>
{/* Featured Yacht Block */}
{featuredYacht && (
<FeaturedYacht yacht={featuredYacht} />
)}
{featuredYacht && <FeaturedYacht yacht={featuredYacht} />}
{/* Yacht Grid */}
{yachtCatalog && (
@ -55,7 +63,7 @@ export default function YachtGrid() {
{yachtCatalog.map((yacht) => (
<Link
key={yacht.id}
href={`/catalog/${yacht.id ?? 0}}`}
href={`/catalog/${yacht.id ?? 0}`}
className="block"
>
<Card className="overflow-hidden bg-white text-gray-900 cursor-pointer transition-all duration-200 hover:shadow-lg">
@ -71,9 +79,7 @@ export default function YachtGrid() {
"url('/images/best-yacht-bg.jpg')",
}}
>
<span>
{yacht.topText}
</span>
<span>{yacht.topText}</span>
</div>
</div>
)}
@ -89,13 +95,8 @@ export default function YachtGrid() {
<>
<div className="absolute top-3 left-3">
<div className="flex items-center justify-center bg-black/40 text-white px-3 py-1 rounded-lg text-sm flex items-center gap-1">
<Icon
size={16}
name="restart"
/>
<span>
По запросу
</span>
<Icon size={16} name="restart" />
<span>По запросу</span>
</div>
</div>
</>
@ -106,9 +107,7 @@ export default function YachtGrid() {
<div className="flex justify-between gap-4">
{/* Левая колонка - название и длина */}
<div className="space-y-2">
<h3 className="font-bold text-l">
{yacht.name}
</h3>
<h3 className="font-bold text-l">{yacht.name}</h3>
<div className="flex items-center gap-1 text-sm">
<Icon size={16} name="width" />
<span>{formatWidth(yacht.length)}</span>

View File

@ -4,30 +4,99 @@ import { Button } from "@/components/ui/button";
import Image from "next/image";
import Link from "next/link";
import { User, ArrowUpRight, Map, ArrowLeft, Heart } from "lucide-react";
import { useState } from "react";
import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import useApiClient from "@/hooks/useApiClient";
import { getImageUrl } from "@/lib/utils";
import { differenceInHours, parseISO } from "date-fns";
export default function ConfirmPage() {
const [yacht, setYacht] = useState<CatalogItemLongDto | null>(null);
const [totalHours, setTotalHours] = useState<number>(0);
const [totalPrice, setTotalPrice] = useState<number>(0);
const client = useApiClient();
const [promocode, setPromocode] = useState("");
const router = useRouter();
const searchParams = useSearchParams();
// Извлекаем параметры из URL
const yachtId = searchParams.get("yachtId");
const guestCount = searchParams.get("guestCount");
const guestCount = searchParams.get("guests");
const departureDate = searchParams.get("departureDate");
const departureTime = searchParams.get("departureTime");
const arrivalDate = searchParams.get("arrivalDate");
const arrivalTime = searchParams.get("arrivalTime");
useEffect(() => {
(async () => {
const response = await client.get<CatalogItemLongDto>(
`/catalog/${yachtId}/`
);
setYacht(response.data);
})();
}, [yachtId]);
// Расчет стоимости при изменении дат
useEffect(() => {
if (
departureDate &&
departureTime &&
arrivalDate &&
arrivalTime &&
yacht?.minCost
) {
try {
// Создаем полные даты
const departureDateTime = parseISO(`${departureDate}T${departureTime}`);
const arrivalDateTime = parseISO(`${arrivalDate}T${arrivalTime}`);
// Рассчитываем разницу в часах (с округлением до 0.5 часа)
let hoursDiff = differenceInHours(arrivalDateTime, departureDateTime);
// Добавляем разницу в минутах
const minutesDiff =
(arrivalDateTime.getMinutes() - departureDateTime.getMinutes()) / 60;
hoursDiff += minutesDiff;
// Округляем до ближайших 0.5 часа
const roundedHours = Math.ceil(hoursDiff * 2) / 2;
// Рассчитываем стоимость
const pricePerHour = yacht.minCost;
const total = pricePerHour * roundedHours;
setTotalHours(roundedHours);
setTotalPrice(total);
} catch (error) {
console.error("Error calculating price:", error);
setTotalHours(0);
setTotalPrice(0);
}
} else {
setTotalHours(0);
setTotalPrice(0);
}
}, [departureDate, departureTime, arrivalDate, arrivalTime, yacht]);
// Функция для форматирования даты (краткий формат)
const formatDate = (dateString: string | null) => {
if (!dateString) return null;
try {
const date = new Date(dateString);
const date = parseISO(dateString);
const months = [
"янв", "фев", "мар", "апр", "май", "июн",
"июл", "авг", "сен", "окт", "ноя", "дек"
"янв",
"фев",
"мар",
"апр",
"май",
"июн",
"июл",
"авг",
"сен",
"окт",
"ноя",
"дек",
];
const day = date.getDate();
const month = months[date.getMonth()];
@ -41,10 +110,20 @@ export default function ConfirmPage() {
const formatDateFull = (dateString: string | null) => {
if (!dateString) return null;
try {
const date = new Date(dateString);
const date = parseISO(dateString);
const months = [
"января", "февраля", "марта", "апреля", "мая", "июня",
"июля", "августа", "сентября", "октября", "ноября", "декабря"
"января",
"февраля",
"марта",
"апреля",
"мая",
"июня",
"июля",
"августа",
"сентября",
"октября",
"ноября",
"декабря",
];
const day = date.getDate();
const month = months[date.getMonth()];
@ -57,7 +136,6 @@ export default function ConfirmPage() {
// Функция для форматирования времени
const formatTime = (timeString: string | null) => {
if (!timeString) return null;
// Предполагаем формат HH:mm или HH:mm:ss
return timeString.split(":").slice(0, 2).join(":");
};
@ -72,22 +150,39 @@ export default function ConfirmPage() {
const arrivalDateFormattedFull = formatDateFull(arrivalDate);
// Формируем строки для отображения
const departureDisplay = departureDateFormatted && departureTimeFormatted
const departureDisplay =
departureDateFormatted && departureTimeFormatted
? `${departureDateFormatted} ${departureTimeFormatted}`
: "Не выбрано";
const arrivalDisplay = arrivalDateFormatted && arrivalTimeFormatted
const arrivalDisplay =
arrivalDateFormatted && arrivalTimeFormatted
? `${arrivalDateFormatted} ${arrivalTimeFormatted}`
: "Не выбрано";
const datesDisplay = departureDateFormattedFull && departureTimeFormatted && arrivalDateFormattedFull && arrivalTimeFormatted
const datesDisplay =
departureDateFormattedFull &&
departureTimeFormatted &&
arrivalDateFormattedFull &&
arrivalTimeFormatted
? `${departureDateFormattedFull} в ${departureTimeFormatted}${arrivalDateFormattedFull} в ${arrivalTimeFormatted}`
: "Не выбрано";
const guestsDisplay = guestCount
? guestCount === "1" ? "1 гость" : `${guestCount} гостей`
? guestCount === "1"
? "1 гость"
: `${guestCount} гостей`
: "Не выбрано";
// Форматирование цены с разделителями тысяч
const formatPrice = (price: number) => {
return new Intl.NumberFormat("ru-RU").format(price);
};
if (!yacht) {
return <div />;
}
return (
<main className="bg-[#f4f4f4] grow">
{/* Мобильная версия */}
@ -101,16 +196,13 @@ export default function ConfirmPage() {
onClick={() => router.back()}
className="flex-shrink-0 w-10 h-10 rounded-full border border-[#DFDFDF] flex items-center justify-center hover:bg-[#f4f4f4] transition-colors"
>
<ArrowLeft
size={20}
className="text-[#333333]"
/>
<ArrowLeft size={20} className="text-[#333333]" />
</button>
{/* Центральный блок с информацией */}
<div className="flex-1 min-w-0 text-center">
<h2 className="text-base font-bold text-[#333333] mb-1">
Яхта Сеньорита
Яхта {yacht.name}
</h2>
<div className="flex justify-center gap-10 text-xs text-[#666666]">
<span>{departureDateFormatted || "Не выбрано"}</span>
@ -120,10 +212,7 @@ export default function ConfirmPage() {
{/* Кнопка избранного */}
<button className="flex-shrink-0 w-10 h-10 flex items-center justify-center hover:opacity-70 transition-opacity">
<Heart
size={20}
className="text-[#333333] stroke-2"
/>
<Heart size={20} className="text-[#333333] stroke-2" />
</button>
</div>
</div>
@ -133,9 +222,7 @@ export default function ConfirmPage() {
<div className="bg-white p-4">
{/* Заголовок с иконкой */}
<div className="flex items-center gap-2 mb-6">
<h1 className="text-xl text-[#333333]">
Ваше бронирование 🛥
</h1>
<h1 className="text-xl text-[#333333]">Ваше бронирование 🛥</h1>
</div>
{/* Поля Выход и Заход */}
@ -145,9 +232,7 @@ export default function ConfirmPage() {
Выход
</label>
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
<div className="text-[#333333]">
{departureDisplay}
</div>
<div className="text-[#333333]">{departureDisplay}</div>
</div>
</div>
<div className="relative">
@ -155,9 +240,7 @@ export default function ConfirmPage() {
Заход
</label>
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
<div className="text-[#333333]">
{arrivalDisplay}
</div>
<div className="text-[#333333]">{arrivalDisplay}</div>
</div>
</div>
</div>
@ -175,9 +258,7 @@ export default function ConfirmPage() {
Гостей
</label>
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF] flex items-center justify-between">
<span className="text-[#333333]">
{guestsDisplay}
</span>
<span className="text-[#333333]">{guestsDisplay}</span>
</div>
</div>
</div>
@ -188,8 +269,7 @@ export default function ConfirmPage() {
Правила отмены
</h3>
<p className="text-base text-[#333333]">
При отмене до 10 мая вы получите частичный
возврат.{" "}
При отмене до 10 мая вы получите частичный возврат.{" "}
<Link
href="#"
className="text-sm text-[#2D908D] hover:text-[#007088] font-bold transition-colors"
@ -205,28 +285,32 @@ export default function ConfirmPage() {
Детализация цены
</h3>
<div>
{totalHours > 0 && yacht.minCost ? (
<>
<div className="flex justify-between items-center mb-4">
<span className="text-[#333333]">
26 400 x 2ч
{formatPrice(yacht.minCost)} × {totalHours}ч
</span>
<span className="text-[#333333]">
52 800
{formatPrice(totalPrice)}
</span>
</div>
<div className="flex justify-between items-center mb-4 pb-4 border-b border-[#DFDFDF]">
<span className="text-[#333333]">
Услуги
</span>
<span className="text-[#333333]">Услуги</span>
<span className="text-[#333333]">0 Р</span>
</div>
<div className="flex justify-between items-center">
<span className="text-[#333333]">
Итого:
</span>
<span className="text-[#333333]">Итого:</span>
<span className="font-bold text-[#333333]">
52 800 Р
{formatPrice(totalPrice)} Р
</span>
</div>
</>
) : (
<div className="text-[#999999] text-center py-4">
Укажите даты для расчета стоимости
</div>
)}
</div>
</div>
@ -237,9 +321,7 @@ export default function ConfirmPage() {
type="text"
placeholder="Промокод"
value={promocode}
onChange={(e) =>
setPromocode(e.target.value)
}
onChange={(e) => setPromocode(e.target.value)}
className="flex-1 min-w-0 px-4 py-3 h-[64px] border border-[#DFDFDF] rounded-full text-sm text-[#757575] focus:outline-none focus:ring-2 focus:ring-[#008299] focus:border-transparent"
/>
<Button
@ -255,6 +337,7 @@ export default function ConfirmPage() {
<Button
variant="default"
className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200"
disabled={totalHours === 0}
>
Отправить заявку
</Button>
@ -273,9 +356,7 @@ export default function ConfirmPage() {
</span>
</Link>
<span>&gt;</span>
<span className="text-[#333333]">
Ваше бронирование
</span>
<span className="text-[#333333]">Ваше бронирование</span>
</div>
<div className="flex flex-col lg:flex-row gap-6">
@ -286,7 +367,7 @@ export default function ConfirmPage() {
{/* Изображение яхты */}
<div className="relative mb-5">
<Image
src="/images/yachts/yacht1.jpg"
src={getImageUrl(yacht.mainImageUrl)}
alt="Яхта"
width={400}
height={250}
@ -295,16 +376,11 @@ export default function ConfirmPage() {
{/* Плашка владельца */}
<div className="absolute top-2 left-2">
<div className="bg-white backdrop-blur-sm px-4 py-2 rounded-[8px] flex items-center gap-2">
<User
size={22}
className="text-[#999999]"
/>
<User size={22} className="text-[#999999]" />
<div className="flex flex-col gap-[4px]">
<span className="text-[#999999]">
Владелец
</span>
<span className="text-[#999999]">Владелец</span>
<span className="text-[#333333] font-bold">
Денис
{yacht.owner.firstName}
</span>
</div>
</div>
@ -312,7 +388,7 @@ export default function ConfirmPage() {
</div>
{/* Название яхты */}
<h3 className="text-base text-[#333333] pb-3 border-b border-[#DFDFDF] mb-4">
Яхта
Яхта {yacht.name}
</h3>
{/* Детализация цены */}
@ -321,30 +397,32 @@ export default function ConfirmPage() {
Детализация цены
</h4>
<div>
{totalHours > 0 && yacht.minCost ? (
<>
<div className="flex justify-between items-center mb-4">
<span className="text-[#333333]">
26 400 x 2ч
{formatPrice(yacht.minCost)} × {totalHours}ч
</span>
<span className="text-[#333333]">
52 800
{formatPrice(totalPrice)}
</span>
</div>
<div className="flex justify-between items-center border-b border-[#DFDFDF] pb-4 mb-4">
<span className="text-[#333333]">
Услуги
</span>
<span className="text-[#333333]">
0 Р
</span>
<span className="text-[#333333]">Услуги</span>
<span className="text-[#333333]">0 Р</span>
</div>
<div className="flex justify-between items-center">
<span className="text-[#333333]">
Итого:
</span>
<span className="text-[#333333]">Итого:</span>
<span className="text-[#333333] font-bold">
52 800 Р
{formatPrice(totalPrice)} Р
</span>
</div>
</>
) : (
<div className="text-[#999999] text-center py-4">
Укажите даты для расчета стоимости
</div>
)}
</div>
</div>
</div>
@ -358,9 +436,7 @@ export default function ConfirmPage() {
type="text"
placeholder="Промокод"
value={promocode}
onChange={(e) =>
setPromocode(e.target.value)
}
onChange={(e) => setPromocode(e.target.value)}
className="flex-1 min-w-0 px-4 sm:px-8 py-5 h-[64px] border border-[#DFDFDF] rounded-full text-base text-[#757575] focus:outline-none focus:ring-2 focus:ring-[#008299] focus:border-transparent"
/>
<Button
@ -385,13 +461,11 @@ export default function ConfirmPage() {
Ваше бронирование
</h2>
{/* Сведения о бронировании */}
{/* Сведения о бронирования */}
<div className="grid grid-cols-2 gap-4 mb-4">
{/* Даты */}
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
<div className="text-[#333333] mb-1">
Даты
</div>
<div className="text-[#333333] mb-1">Даты</div>
<div className="text-base text-[#999999]">
{datesDisplay}
</div>
@ -400,9 +474,7 @@ export default function ConfirmPage() {
{/* Гости */}
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
<div>
<div className="text-[#333333] mb-1">
Гости
</div>
<div className="text-[#333333] mb-1">Гости</div>
<div className="text-base text-[#999999]">
{guestsDisplay}
</div>
@ -423,8 +495,7 @@ export default function ConfirmPage() {
</h3>
<p className="text-[#333333]">
При отмене до 10 мая вы получите частичный
возврат.
При отмене до 10 мая вы получите частичный возврат.
</p>
<Link
href="#"
@ -445,6 +516,7 @@ export default function ConfirmPage() {
variant="default"
size="lg"
className="flex-shrink-0 h-[64px] w-[270px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full p-0 transition-colors duration-200 hover:shadow-lg"
disabled={totalHours === 0}
>
Отправить заявку
</Button>

View File

@ -62,9 +62,7 @@ const CommonPopoverContent: React.FC<CommonPopoverContentProps> = ({
onSelect={setDate}
className="mb-[24px]"
locale={ru}
disabled={(date) =>
date < new Date(new Date().setHours(0, 0, 0, 0))
}
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
classNames={{
root: "w-full",
month: "flex w-full flex-col gap-4",
@ -76,8 +74,7 @@ const CommonPopoverContent: React.FC<CommonPopoverContentProps> = ({
"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",
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",
@ -88,7 +85,6 @@ const CommonPopoverContent: React.FC<CommonPopoverContentProps> = ({
}}
/>
{/* Счетчики гостей */}
<div className="mb-[24px] flex gap-3">
<Counter
@ -217,10 +213,7 @@ export const GuestDatePicker: React.FC<GuestDatePickerProps> = ({
<div className={className}>
<div className="space-y-5">
{/* Кнопка Выход */}
<Popover
open={isDepartureOpen}
onOpenChange={setIsDepartureOpen}
>
<Popover open={isDepartureOpen} onOpenChange={setIsDepartureOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"

View File

@ -24,6 +24,8 @@ interface DatePickerProps {
onDateChange?: (date: Date | undefined) => void;
onDepartureTimeChange?: (time: string) => void;
onArrivalTimeChange?: (time: string) => void;
onlyDeparture?: boolean;
onlyArrival?: boolean;
}
export function DatePicker({
@ -36,19 +38,31 @@ export function DatePicker({
onDateChange,
onDepartureTimeChange,
onArrivalTimeChange,
onlyDeparture,
onlyArrival,
}: DatePickerProps) {
const [internalDate, setInternalDate] = React.useState<Date>();
const [internalDepartureTime, setInternalDepartureTime] = React.useState("12:00");
const [internalDepartureTime, setInternalDepartureTime] =
React.useState("12:00");
const [internalArrivalTime, setInternalArrivalTime] = React.useState("13:00");
const [open, setOpen] = React.useState(false);
// Определяем, является ли компонент контролируемым
const isControlled = value !== undefined || externalDepartureTime !== undefined || externalArrivalTime !== undefined;
const isControlled =
value !== undefined ||
externalDepartureTime !== undefined ||
externalArrivalTime !== undefined;
// Используем внешние значения, если они предоставлены, иначе внутренние
const date = value !== undefined ? (value || undefined) : internalDate;
const departureTime = externalDepartureTime !== undefined ? externalDepartureTime : internalDepartureTime;
const arrivalTime = externalArrivalTime !== undefined ? externalArrivalTime : internalArrivalTime;
const date = value !== undefined ? value || undefined : internalDate;
const departureTime =
externalDepartureTime !== undefined
? externalDepartureTime
: internalDepartureTime;
const arrivalTime =
externalArrivalTime !== undefined
? externalArrivalTime
: internalArrivalTime;
const handleDateChange = (newDate: Date | undefined) => {
if (onDateChange) {
@ -91,17 +105,12 @@ export function DatePicker({
>
<div className="flex items-center">
{showIcon && (
<Icon
name="calendar"
className="w-4 h-4 text-brand mr-2"
/>
<Icon name="calendar" className="w-4 h-4 text-brand mr-2" />
)}
{date ? (
format(
date,
`d MMMM, ${departureTime} - ${arrivalTime}`,
{ locale: ru }
)
format(date, `d MMMM, ${departureTime} - ${arrivalTime}`, {
locale: ru,
})
) : (
<span>{placeholder}</span>
)}
@ -150,6 +159,7 @@ export function DatePicker({
{/* Поля времени */}
<div className="flex gap-3 mb-4">
{!onlyDeparture ? (
<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">
Выход
@ -159,14 +169,14 @@ export function DatePicker({
<input
type="time"
value={departureTime}
onChange={(e) =>
handleDepartureTimeChange(e.target.value)
}
onChange={(e) => handleDepartureTimeChange(e.target.value)}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
) : null}
{!onlyArrival ? (
<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">
Заход
@ -176,13 +186,12 @@ export function DatePicker({
<input
type="time"
value={arrivalTime}
onChange={(e) =>
handleArrivalTimeChange(e.target.value)
}
onChange={(e) => handleArrivalTimeChange(e.target.value)}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
) : null}
</div>
{/* Кнопка Применить */}

View File

@ -7,7 +7,7 @@ const useApiClient = () => {
const authPopup = useAuthPopup();
const apiClient = axios.create({
baseURL: "http://192.168.1.5:4000/",
baseURL: "/api",
headers: {
"Content-Type": "application/json",
},