Add all possible bullshit
This commit is contained in:
parent
d177eee970
commit
745d58ab3a
|
|
@ -1,4 +1,4 @@
|
||||||
interface CatalogItemDto {
|
interface CatalogItemShortDto {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
length: number;
|
length: number;
|
||||||
|
|
@ -13,11 +13,65 @@ interface CatalogItemDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainPageCatalogResponseDto {
|
interface MainPageCatalogResponseDto {
|
||||||
featuredYacht: CatalogItemDto;
|
featuredYacht: CatalogItemShortDto;
|
||||||
restYachts: CatalogItemDto[];
|
restYachts: CatalogItemShortDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CatalogFilteredResponseDto {
|
interface CatalogFilteredResponseDto {
|
||||||
items: CatalogItemDto[];
|
items: CatalogItemShortDto[];
|
||||||
total: number;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,17 @@ import { useRouter } from "next/navigation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DatePicker } from "@/components/ui/date-picker";
|
import { DatePicker } from "@/components/ui/date-picker";
|
||||||
import { GuestPicker } from "@/components/form/guest-picker";
|
import { GuestPicker } from "@/components/form/guest-picker";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
interface BookingWidgetProps {
|
interface BookingWidgetProps {
|
||||||
price: string;
|
price: string;
|
||||||
|
yacht: CatalogItemLongDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BookingWidget({ price }: BookingWidgetProps) {
|
export function BookingWidget({ price, yacht }: BookingWidgetProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [departureDate] = useState<Date | undefined>();
|
const [departureDate, setDepartureDate] = useState<Date | undefined>();
|
||||||
const [arrivalDate] = useState<Date | undefined>();
|
const [arrivalDate, setArrivalDate] = useState<Date | undefined>();
|
||||||
const [guests, setGuests] = useState({ adults: 1, children: 0 });
|
const [guests, setGuests] = useState({ adults: 1, children: 0 });
|
||||||
const [total] = useState(0);
|
const [total] = useState(0);
|
||||||
|
|
||||||
|
|
@ -22,13 +24,18 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBook = () => {
|
const handleBook = () => {
|
||||||
// Логика бронирования
|
if (!departureDate || !arrivalDate || !yacht || !yacht.id) return;
|
||||||
console.log("Booking:", {
|
|
||||||
departureDate,
|
const params = new URLSearchParams({
|
||||||
arrivalDate,
|
yachtId: yacht.id.toString(),
|
||||||
guests,
|
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 (
|
return (
|
||||||
|
|
@ -36,9 +43,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<p className="text-2xl font-bold text-[#333333] mb-2">
|
<p className="text-2xl font-bold text-[#333333] mb-2">
|
||||||
от {price} ₽{" "}
|
от {price} ₽{" "}
|
||||||
<span className="text-base font-normal text-[#999999]">
|
<span className="text-base font-normal text-[#999999]">/час</span>
|
||||||
/час
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -51,6 +56,9 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
variant="small"
|
variant="small"
|
||||||
placeholder="Выберите дату и время"
|
placeholder="Выберите дату и время"
|
||||||
showIcon={false}
|
showIcon={false}
|
||||||
|
onDateChange={setDepartureDate}
|
||||||
|
value={departureDate}
|
||||||
|
onlyDeparture
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -62,6 +70,9 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
variant="small"
|
variant="small"
|
||||||
placeholder="Выберите дату и время"
|
placeholder="Выберите дату и время"
|
||||||
showIcon={false}
|
showIcon={false}
|
||||||
|
onDateChange={setArrivalDate}
|
||||||
|
value={arrivalDate}
|
||||||
|
onlyArrival
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -84,6 +95,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
onClick={handleBook}
|
onClick={handleBook}
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
className="w-full h-12 font-bold text-white mb-4"
|
className="w-full h-12 font-bold text-white mb-4"
|
||||||
|
disabled={!departureDate || !arrivalDate}
|
||||||
>
|
>
|
||||||
Забронировать
|
Забронировать
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -91,9 +103,7 @@ export function BookingWidget({ price }: BookingWidgetProps) {
|
||||||
<div className="pt-4 border-t border-gray-200">
|
<div className="pt-4 border-t border-gray-200">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-base text-[#333333]">Итого:</span>
|
<span className="text-base text-[#333333]">Итого:</span>
|
||||||
<span className="text-base font-bold text-[#333333]">
|
<span className="text-base font-bold text-[#333333]">{total} ₽</span>
|
||||||
{total} ₽
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,7 @@
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
interface ContactInfoProps {
|
export function ContactInfo({ firstName, companyName, inn, ogrn }: User) {
|
||||||
contactPerson: {
|
|
||||||
name: string;
|
|
||||||
avatar: string;
|
|
||||||
};
|
|
||||||
requisites: {
|
|
||||||
ip: string;
|
|
||||||
inn: string;
|
|
||||||
ogrn: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContactInfo({ contactPerson, requisites }: ContactInfoProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col sm:flex-row gap-5">
|
<div className="flex flex-col sm:flex-row gap-5">
|
||||||
<div className="flex-1 rounded-[24px] px-6 py-5 bg-[#f4f4f4]">
|
<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">
|
<div className="relative rounded-full overflow-hidden bg-gray-200 flex items-center justify-center">
|
||||||
<Image
|
<Image
|
||||||
src="/images/avatar.png"
|
src="/images/avatar.png"
|
||||||
alt={contactPerson.name}
|
alt={firstName || "avatar"}
|
||||||
width={124}
|
width={124}
|
||||||
height={124}
|
height={124}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-between h-full">
|
<div className="flex flex-col justify-between h-full">
|
||||||
<h3 className="text-base font-bold text-[#333333]">
|
<h3 className="text-base font-bold text-[#333333]">{firstName}</h3>
|
||||||
{contactPerson.name}
|
<p className="text-base text-[#333333]">Контактное лицо</p>
|
||||||
</h3>
|
|
||||||
<p className="text-base text-[#333333]">
|
|
||||||
Контактное лицо
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 rounded-[24px] px-6 py-5 bg-[#f4f4f4]">
|
<div className="flex-1 rounded-[24px] px-6 py-5 bg-[#f4f4f4]">
|
||||||
<h3 className="text-base font-bold text-[#333333] mb-3">
|
<h3 className="text-base font-bold text-[#333333] mb-3">Реквизиты</h3>
|
||||||
Реквизиты
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
|
<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-[#333333]">ИП</span>
|
||||||
<span className="text-base text-[#999999]">
|
<span className="text-base text-[#999999]">{companyName}</span>
|
||||||
{requisites.ip}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
|
<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-[#333333]">ИНН</span>
|
||||||
<span className="text-base text-[#999999]">
|
<span className="text-base text-[#999999]">{inn}</span>
|
||||||
{requisites.inn}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
|
<div className="flex flex-col sm:flex-row sm:justify-between gap-1">
|
||||||
<span className="text-base text-[#333333]">
|
<span className="text-base text-[#333333]">ОГРН/ОГРНИП</span>
|
||||||
ОГРН/ОГРНИП
|
<span className="text-base text-[#999999]">{ogrn}</span>
|
||||||
</span>
|
|
||||||
<span className="text-base text-[#999999]">
|
|
||||||
{requisites.ogrn}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import {
|
import {
|
||||||
isSameMonth,
|
isSameMonth,
|
||||||
|
|
@ -14,14 +14,24 @@ import {
|
||||||
import { ru } from "date-fns/locale";
|
import { ru } from "date-fns/locale";
|
||||||
import { ChevronLeftIcon, ChevronRightIcon, Clock } from "lucide-react";
|
import { ChevronLeftIcon, ChevronRightIcon, Clock } from "lucide-react";
|
||||||
|
|
||||||
|
interface Reservation {
|
||||||
|
id: number;
|
||||||
|
reservatorId: number;
|
||||||
|
yachtId: number;
|
||||||
|
startUtc: number;
|
||||||
|
endUtc: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface YachtAvailabilityProps {
|
interface YachtAvailabilityProps {
|
||||||
price: string;
|
price: string;
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
|
reservations?: Reservation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function YachtAvailability({
|
export function YachtAvailability({
|
||||||
price,
|
price,
|
||||||
mobile = false,
|
mobile = false,
|
||||||
|
reservations = [],
|
||||||
}: YachtAvailabilityProps) {
|
}: YachtAvailabilityProps) {
|
||||||
const today = startOfDay(new Date());
|
const today = startOfDay(new Date());
|
||||||
const [currentMonth, setCurrentMonth] = useState(
|
const [currentMonth, setCurrentMonth] = useState(
|
||||||
|
|
@ -34,6 +44,62 @@ export function YachtAvailability({
|
||||||
return new Date(2025, 3, i + 1);
|
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) => {
|
const isDateUnavailable = (date: Date) => {
|
||||||
return unavailableDates.some(
|
return unavailableDates.some(
|
||||||
(d) =>
|
(d) =>
|
||||||
|
|
@ -48,7 +114,6 @@ export function YachtAvailability({
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldBeCrossedOut = (date: Date) => {
|
const shouldBeCrossedOut = (date: Date) => {
|
||||||
// Перечеркиваем если день занят или находится до текущего дня
|
|
||||||
return isDateUnavailable(date) || isDateInPast(date);
|
return isDateUnavailable(date) || isDateInPast(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,6 +153,36 @@ export function YachtAvailability({
|
||||||
return { value: timeString, label: timeString };
|
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:00—20: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) {
|
if (mobile) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -126,15 +221,7 @@ export function YachtAvailability({
|
||||||
locale={ru}
|
locale={ru}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatWeekdayName: (date) => {
|
formatWeekdayName: (date) => {
|
||||||
const weekdays = [
|
const weekdays = ["ВС", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"];
|
||||||
"ВС",
|
|
||||||
"ПН",
|
|
||||||
"ВТ",
|
|
||||||
"СР",
|
|
||||||
"ЧТ",
|
|
||||||
"ПТ",
|
|
||||||
"СБ",
|
|
||||||
];
|
|
||||||
return weekdays[date.getDay()];
|
return weekdays[date.getDay()];
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
@ -159,9 +246,8 @@ export function YachtAvailability({
|
||||||
return <div className="hidden" />;
|
return <div className="hidden" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCrossedOut = shouldBeCrossedOut(
|
const isCrossedOut = shouldBeCrossedOut(day.date);
|
||||||
day.date
|
const hasRes = hasReservationsOnDate(day.date);
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
@ -180,6 +266,9 @@ export function YachtAvailability({
|
||||||
disabled={isCrossedOut}
|
disabled={isCrossedOut}
|
||||||
>
|
>
|
||||||
{day.date.getDate()}
|
{day.date.getDate()}
|
||||||
|
{hasRes && !isCrossedOut && (
|
||||||
|
<div className="absolute bottom-1 right-1 w-1.5 h-1.5 bg-[#2F5CD0] rounded-full"></div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -231,9 +320,7 @@ export function YachtAvailability({
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-base font-bold text-[#333333]">
|
<h2 className="text-base font-bold text-[#333333]">Доступность яхты</h2>
|
||||||
Доступность яхты
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white w-full">
|
<div className="bg-white w-full">
|
||||||
|
|
@ -273,8 +360,7 @@ export function YachtAvailability({
|
||||||
button_next: "hidden",
|
button_next: "hidden",
|
||||||
table: "w-full border-collapse",
|
table: "w-full border-collapse",
|
||||||
weekdays: "hidden",
|
weekdays: "hidden",
|
||||||
weekday:
|
weekday: "flex-1 text-gray-500 text-xs font-normal p-2 text-center",
|
||||||
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
|
|
||||||
week: "flex w-full",
|
week: "flex w-full",
|
||||||
day: "relative flex-1",
|
day: "relative flex-1",
|
||||||
}}
|
}}
|
||||||
|
|
@ -311,17 +397,16 @@ export function YachtAvailability({
|
||||||
{/* Дата и "Доступно:" в одной строке */}
|
{/* Дата и "Доступно:" в одной строке */}
|
||||||
<div className="flex items-center justify-between w-full">
|
<div className="flex items-center justify-between w-full">
|
||||||
<span className="text-xs text-gray-400">
|
<span className="text-xs text-gray-400">
|
||||||
Доступно:
|
{hasReservationsOnDate(day.date)
|
||||||
|
? "Бронь:"
|
||||||
|
: "Доступно:"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-medium text-[#333333]">
|
<span className="text-sm font-medium text-[#333333]">
|
||||||
{day.date.getDate()}
|
{day.date.getDate()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5 w-full mt-1">
|
{/* Time slots - reservations first */}
|
||||||
<div className="w-fit bg-[#F6BD4D] text-white text-[10px] font-medium px-1 py-0 rounded-full inline-block">
|
{renderTimeSlots(day.date)}
|
||||||
08:00—20:00
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Цена в нижнем правом углу */}
|
{/* Цена в нижнем правом углу */}
|
||||||
<span className="absolute bottom-[2px] right-[4px] text-xs text-[#333333] font-medium">
|
<span className="absolute bottom-[2px] right-[4px] text-xs text-[#333333] font-medium">
|
||||||
{price} / час
|
{price} / час
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
interface YachtCharacteristicsProps {
|
interface YachtCharacteristicsProps {
|
||||||
yacht: {
|
yacht: CatalogItemLongDto;
|
||||||
year: number;
|
|
||||||
maxCapacity: number;
|
|
||||||
comfortableCapacity: number;
|
|
||||||
length: number;
|
|
||||||
width: number;
|
|
||||||
cabins: number;
|
|
||||||
material: string;
|
|
||||||
power: number;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
|
export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
|
||||||
|
|
@ -22,12 +13,12 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Комфортная вместимость",
|
label: "Комфортная вместимость",
|
||||||
value: `${yacht.comfortableCapacity} человек`,
|
value: `${yacht.comfortCapacity} человек`,
|
||||||
},
|
},
|
||||||
{ label: "Длина", value: `${yacht.length} м` },
|
{ label: "Длина", value: `${yacht.length} м` },
|
||||||
{ label: "Ширина", value: `${yacht.width} м` },
|
{ label: "Ширина", value: `${yacht.width} м` },
|
||||||
{ label: "Каюты", value: yacht.cabins },
|
{ label: "Каюты", value: yacht.cabinsCount },
|
||||||
{ label: "Материал", value: yacht.material },
|
{ label: "Материал", value: yacht.matherial },
|
||||||
{ label: "Мощность", value: `${yacht.power} л/с` },
|
{ label: "Мощность", value: `${yacht.power} л/с` },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -42,9 +33,7 @@ export function YachtCharacteristics({ yacht }: YachtCharacteristicsProps) {
|
||||||
key={index}
|
key={index}
|
||||||
className="flex justify-between items-center py-4 border-b border-gray-200"
|
className="flex justify-between items-center py-4 border-b border-gray-200"
|
||||||
>
|
>
|
||||||
<span className="text-base text-[#999999]">
|
<span className="text-base text-[#999999]">{char.label}</span>
|
||||||
{char.label}
|
|
||||||
</span>
|
|
||||||
<span className="text-base font-regular text-[#333333]">
|
<span className="text-base font-regular text-[#333333]">
|
||||||
{char.value}
|
{char.value}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
type CarouselApi,
|
type CarouselApi,
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
|
import { getImageUrl } from "@/lib/utils";
|
||||||
|
|
||||||
interface YachtGalleryProps {
|
interface YachtGalleryProps {
|
||||||
images: string[];
|
images: string[];
|
||||||
|
|
@ -54,7 +55,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) {
|
||||||
<CarouselItem key={index}>
|
<CarouselItem key={index}>
|
||||||
<div className="relative w-full h-[60vh] lg:h-[592px] rounded-0 lg:rounded-[24px] overflow-hidden">
|
<div className="relative w-full h-[60vh] lg:h-[592px] rounded-0 lg:rounded-[24px] overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
src={img}
|
src={getImageUrl(img)}
|
||||||
alt={`Yacht image ${index + 1}`}
|
alt={`Yacht image ${index + 1}`}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|
@ -97,7 +98,7 @@ export function YachtGallery({ images, badge }: YachtGalleryProps) {
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={img}
|
src={getImageUrl(img)}
|
||||||
alt={`Thumbnail ${index + 1}`}
|
alt={`Thumbnail ${index + 1}`}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|
|
||||||
|
|
@ -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",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter, useParams } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ArrowLeft, Heart } from "lucide-react";
|
import { ArrowLeft, Heart } from "lucide-react";
|
||||||
import Icon from "@/components/ui/icon";
|
import Icon from "@/components/ui/icon";
|
||||||
import { YachtGallery } from "./components/YachtGallery";
|
import { YachtGallery } from "./components/YachtGallery";
|
||||||
|
|
@ -10,9 +10,23 @@ import { YachtAvailability } from "./components/YachtAvailability";
|
||||||
import { BookingWidget } from "./components/BookingWidget";
|
import { BookingWidget } from "./components/BookingWidget";
|
||||||
import { YachtCharacteristics } from "./components/YachtCharacteristics";
|
import { YachtCharacteristics } from "./components/YachtCharacteristics";
|
||||||
import { ContactInfo } from "./components/ContactInfo";
|
import { ContactInfo } from "./components/ContactInfo";
|
||||||
import { YACHT } from "./const";
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
|
import { formatSpeed } from "@/lib/utils";
|
||||||
|
|
||||||
export default function YachtDetailPage() {
|
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 params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<
|
||||||
|
|
@ -24,6 +38,10 @@ export default function YachtDetailPage() {
|
||||||
| "reviews"
|
| "reviews"
|
||||||
>("availability");
|
>("availability");
|
||||||
|
|
||||||
|
if (!yacht) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="bg-[#f4f4f4] min-h-screen ">
|
<main className="bg-[#f4f4f4] min-h-screen ">
|
||||||
{/* Мобильная фиксированная верхняя панель навигации */}
|
{/* Мобильная фиксированная верхняя панель навигации */}
|
||||||
|
|
@ -35,9 +53,7 @@ export default function YachtDetailPage() {
|
||||||
>
|
>
|
||||||
<ArrowLeft size={24} className="text-[#333333]" />
|
<ArrowLeft size={24} className="text-[#333333]" />
|
||||||
</button>
|
</button>
|
||||||
<h2 className="text-base font-medium text-[#333333]">
|
<h2 className="text-base font-medium text-[#333333]">Яхта</h2>
|
||||||
Яхта
|
|
||||||
</h2>
|
|
||||||
<button className="flex items-center justify-center">
|
<button className="flex items-center justify-center">
|
||||||
<Heart size={24} className="text-[#333333]" />
|
<Heart size={24} className="text-[#333333]" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -59,7 +75,7 @@ export default function YachtDetailPage() {
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span>></span>
|
<span>></span>
|
||||||
<span className="text-[#333333]">{YACHT.name}</span>
|
<span className="text-[#333333]">{yacht.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -70,14 +86,14 @@ export default function YachtDetailPage() {
|
||||||
<div className="lg:hidden pt-[50px]">
|
<div className="lg:hidden pt-[50px]">
|
||||||
{/* Gallery */}
|
{/* Gallery */}
|
||||||
<YachtGallery
|
<YachtGallery
|
||||||
images={YACHT.images}
|
images={yacht.galleryUrls || []}
|
||||||
badge={YACHT.badge}
|
badge={!yacht.hasQuickRent ? "По запросу" : ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Yacht Title */}
|
{/* Yacht Title */}
|
||||||
<div className="px-4 pt-4">
|
<div className="px-4 pt-4">
|
||||||
<h1 className="text-xl font-bold text-[#333333] mb-4">
|
<h1 className="text-xl font-bold text-[#333333] mb-4">
|
||||||
{YACHT.name}
|
{yacht.name}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -105,9 +121,7 @@ export default function YachtDetailPage() {
|
||||||
Описание
|
Описание
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => setActiveTab("characteristics")}
|
||||||
setActiveTab("characteristics")
|
|
||||||
}
|
|
||||||
className={`pb-3 text-base font-medium transition-colors whitespace-nowrap ${
|
className={`pb-3 text-base font-medium transition-colors whitespace-nowrap ${
|
||||||
activeTab === "characteristics"
|
activeTab === "characteristics"
|
||||||
? "text-[#008299] border-b-2 border-[#008299]"
|
? "text-[#008299] border-b-2 border-[#008299]"
|
||||||
|
|
@ -143,26 +157,22 @@ export default function YachtDetailPage() {
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
{activeTab === "availability" && (
|
{activeTab === "availability" && (
|
||||||
<YachtAvailability
|
<YachtAvailability
|
||||||
price={YACHT.price}
|
price={String(yacht.minCost)}
|
||||||
mobile={true}
|
mobile={true}
|
||||||
|
reservations={yacht.reservations}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === "description" && (
|
{activeTab === "description" && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-base text-[#666666] leading-relaxed">
|
<p className="text-base text-[#666666] leading-relaxed">
|
||||||
{YACHT.description}
|
{yacht.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === "characteristics" && (
|
{activeTab === "characteristics" && (
|
||||||
<YachtCharacteristics yacht={YACHT} />
|
<YachtCharacteristics yacht={yacht} />
|
||||||
)}
|
|
||||||
{activeTab === "contact" && (
|
|
||||||
<ContactInfo
|
|
||||||
contactPerson={YACHT.contactPerson}
|
|
||||||
requisites={YACHT.requisites}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
{activeTab === "contact" && <ContactInfo {...yacht.owner} />}
|
||||||
{activeTab === "reviews" && (
|
{activeTab === "reviews" && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
|
@ -186,20 +196,16 @@ export default function YachtDetailPage() {
|
||||||
{/* Yacht Title and Actions */}
|
{/* Yacht Title and Actions */}
|
||||||
<div className="mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
<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]">
|
<h1 className="text-xl md:text-2xl font-bold text-[#333333]">
|
||||||
{YACHT.name}
|
{yacht.name}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="flex items-center gap-2 text-[#333333]">
|
<div className="flex items-center gap-2 text-[#333333]">
|
||||||
<Icon name="pin" size={32} />
|
<Icon name="pin" size={32} />
|
||||||
<span className="text-base">
|
<span className="text-base">{formatSpeed(yacht.speed)}</span>
|
||||||
{YACHT.location}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
|
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
|
||||||
<Icon name="share" size={32} />
|
<Icon name="share" size={32} />
|
||||||
<span className="text-base">
|
<span className="text-base">Поделиться</span>
|
||||||
Поделиться
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
|
<button className="flex items-center gap-2 cursor-pointer text-[#333333] hover:text-[#008299] transition-colors">
|
||||||
<Icon name="heart" size={32} />
|
<Icon name="heart" size={32} />
|
||||||
|
|
@ -212,8 +218,8 @@ export default function YachtDetailPage() {
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Gallery */}
|
{/* Gallery */}
|
||||||
<YachtGallery
|
<YachtGallery
|
||||||
images={YACHT.images}
|
images={yacht.galleryUrls || []}
|
||||||
badge={YACHT.badge}
|
badge={!yacht.hasQuickRent ? "По запросу" : ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Content with Booking Widget on the right */}
|
{/* Content with Booking Widget on the right */}
|
||||||
|
|
@ -221,10 +227,13 @@ export default function YachtDetailPage() {
|
||||||
{/* Left column - all content below gallery */}
|
{/* Left column - all content below gallery */}
|
||||||
<div className="flex-1 space-y-6">
|
<div className="flex-1 space-y-6">
|
||||||
{/* Availability */}
|
{/* Availability */}
|
||||||
<YachtAvailability price={YACHT.price} />
|
<YachtAvailability
|
||||||
|
price={String(yacht.minCost)}
|
||||||
|
reservations={yacht.reservations}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Characteristics */}
|
{/* Characteristics */}
|
||||||
<YachtCharacteristics yacht={YACHT} />
|
<YachtCharacteristics yacht={yacht} />
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -232,15 +241,12 @@ export default function YachtDetailPage() {
|
||||||
Описание
|
Описание
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-base text-[#666666] leading-relaxed">
|
<p className="text-base text-[#666666] leading-relaxed">
|
||||||
{YACHT.description}
|
{yacht.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Contact and Requisites */}
|
{/* Contact and Requisites */}
|
||||||
<ContactInfo
|
<ContactInfo {...yacht.owner} />
|
||||||
contactPerson={YACHT.contactPerson}
|
|
||||||
requisites={YACHT.requisites}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Reviews */}
|
{/* Reviews */}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -260,7 +266,7 @@ export default function YachtDetailPage() {
|
||||||
|
|
||||||
{/* Right column - Booking Widget (sticky) */}
|
{/* Right column - Booking Widget (sticky) */}
|
||||||
<div className="lg:w-74 flex-shrink-0 lg:sticky lg:top-24 self-start">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -273,14 +279,12 @@ export default function YachtDetailPage() {
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-lg font-bold text-[#333333]">
|
<span className="text-lg font-bold text-[#333333]">
|
||||||
{YACHT.price} ₽
|
{yacht.minCost} ₽
|
||||||
</span>
|
|
||||||
<span className="text-sm text-[#999999] ml-1">
|
|
||||||
/ час
|
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-sm text-[#999999] ml-1">/ час</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<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"
|
className="bg-[#008299] text-white px-6 py-3 rounded-lg font-bold text-base hover:bg-[#006d7a] transition-colors"
|
||||||
>
|
>
|
||||||
Забронировать
|
Забронировать
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ import { useState } from "react";
|
||||||
import { GuestDatePicker } from "@/components/form/guest-date-picker";
|
import { GuestDatePicker } from "@/components/form/guest-date-picker";
|
||||||
import { formatMinCost, formatWidth, getImageUrl } from "@/lib/utils";
|
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 [selectedImage, setSelectedImage] = useState(yacht.mainImageUrl);
|
||||||
|
|
||||||
const handleThumbnailClick = (imageSrc: string) => {
|
const handleThumbnailClick = (imageSrc: string) => {
|
||||||
|
|
@ -33,8 +37,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
|
||||||
<div
|
<div
|
||||||
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative lg:hidden"
|
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative lg:hidden"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage:
|
backgroundImage: "url(/images/badge-bg.jpg)",
|
||||||
"url(/images/badge-bg.jpg)",
|
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
|
|
@ -47,14 +50,10 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
|
||||||
|
|
||||||
{/* Header with yacht name and length */}
|
{/* Header with yacht name and length */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-3xl font-bold">
|
<h2 className="text-3xl font-bold">{yacht.name}</h2>
|
||||||
{yacht.name}
|
|
||||||
</h2>
|
|
||||||
<div className="flex items-center gap-2 text-gray-600">
|
<div className="flex items-center gap-2 text-gray-600">
|
||||||
<Icon size={16} name="width" />
|
<Icon size={16} name="width" />
|
||||||
<span className="text-lg">
|
<span className="text-lg">{formatWidth(yacht.length)}</span>
|
||||||
{formatWidth(yacht.length)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -89,20 +88,15 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
src={getImageUrl(thumb)}
|
src={getImageUrl(thumb)}
|
||||||
alt={`${yacht.name
|
alt={`${yacht.name} view ${idx + 1}`}
|
||||||
} view ${idx + 1}`}
|
|
||||||
width={80}
|
width={80}
|
||||||
height={60}
|
height={60}
|
||||||
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${selectedImage ===
|
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${
|
||||||
thumb
|
selectedImage === thumb
|
||||||
? "border-[#008299]"
|
? "border-[#008299]"
|
||||||
: "border-gray-200 hover:border-gray-400"
|
: "border-gray-200 hover:border-gray-400"
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
onClick={() => handleThumbnailClick(thumb)}
|
||||||
handleThumbnailClick(
|
|
||||||
thumb
|
|
||||||
)
|
|
||||||
}
|
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,8 +133,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
|
||||||
<div
|
<div
|
||||||
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative hidden lg:flex"
|
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative hidden lg:flex"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage:
|
backgroundImage: "url(/images/badge-bg.jpg)",
|
||||||
"url(/images/badge-bg.jpg)",
|
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
|
|
@ -177,9 +170,7 @@ export default function FeaturedYacht({ yacht }: { yacht: CatalogItemDto }) {
|
||||||
|
|
||||||
{/* Total price */}
|
{/* Total price */}
|
||||||
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
|
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
|
||||||
<span className="font-normal">
|
<span className="font-normal">Итого:</span>
|
||||||
Итого:
|
|
||||||
</span>
|
|
||||||
<span>0 ₽</span>
|
<span>0 ₽</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,31 @@ import Link from "next/link";
|
||||||
import FeaturedYacht from "./FeaturedYacht";
|
import FeaturedYacht from "./FeaturedYacht";
|
||||||
import useApiClient from "@/hooks/useApiClient";
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
import { useEffect, useState } from "react";
|
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() {
|
export default function YachtGrid() {
|
||||||
const client = useApiClient();
|
const client = useApiClient();
|
||||||
|
|
||||||
const [featuredYacht, setFeaturedYacht] = useState<CatalogItemDto | null>(null);
|
const [featuredYacht, setFeaturedYacht] =
|
||||||
const [yachtCatalog, setYachtCatalog] = useState<CatalogItemDto[] | null>(null);
|
useState<CatalogItemShortDto | null>(null);
|
||||||
|
const [yachtCatalog, setYachtCatalog] = useState<
|
||||||
|
CatalogItemShortDto[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const response = await client.get<MainPageCatalogResponseDto>("/catalog/main-page/");
|
const response = await client.get<MainPageCatalogResponseDto>(
|
||||||
|
"/catalog/main-page/"
|
||||||
|
);
|
||||||
setFeaturedYacht(response.data.featuredYacht);
|
setFeaturedYacht(response.data.featuredYacht);
|
||||||
setYachtCatalog(response.data.restYachts);
|
setYachtCatalog(response.data.restYachts);
|
||||||
})();
|
})();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="text-white">
|
<section className="text-white">
|
||||||
|
|
@ -39,15 +49,13 @@ export default function YachtGrid() {
|
||||||
Каталог лучших яхт Балаклавы разных ценовых сегментах.
|
Каталог лучших яхт Балаклавы разных ценовых сегментах.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed">
|
<p className="text-gray-700 leading-relaxed">
|
||||||
Проверенные лодки с лицензией на перевозки, опытные
|
Проверенные лодки с лицензией на перевозки, опытные капитаны.
|
||||||
капитаны. Выбирайте удобную дату, время и бронируйте.
|
Выбирайте удобную дату, время и бронируйте.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Featured Yacht Block */}
|
{/* Featured Yacht Block */}
|
||||||
{featuredYacht && (
|
{featuredYacht && <FeaturedYacht yacht={featuredYacht} />}
|
||||||
<FeaturedYacht yacht={featuredYacht} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Yacht Grid */}
|
{/* Yacht Grid */}
|
||||||
{yachtCatalog && (
|
{yachtCatalog && (
|
||||||
|
|
@ -55,7 +63,7 @@ export default function YachtGrid() {
|
||||||
{yachtCatalog.map((yacht) => (
|
{yachtCatalog.map((yacht) => (
|
||||||
<Link
|
<Link
|
||||||
key={yacht.id}
|
key={yacht.id}
|
||||||
href={`/catalog/${yacht.id ?? 0}}`}
|
href={`/catalog/${yacht.id ?? 0}`}
|
||||||
className="block"
|
className="block"
|
||||||
>
|
>
|
||||||
<Card className="overflow-hidden bg-white text-gray-900 cursor-pointer transition-all duration-200 hover:shadow-lg">
|
<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')",
|
"url('/images/best-yacht-bg.jpg')",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>{yacht.topText}</span>
|
||||||
{yacht.topText}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -89,13 +95,8 @@ export default function YachtGrid() {
|
||||||
<>
|
<>
|
||||||
<div className="absolute top-3 left-3">
|
<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">
|
<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
|
<Icon size={16} name="restart" />
|
||||||
size={16}
|
<span>По запросу</span>
|
||||||
name="restart"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
По запросу
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -106,9 +107,7 @@ export default function YachtGrid() {
|
||||||
<div className="flex justify-between gap-4">
|
<div className="flex justify-between gap-4">
|
||||||
{/* Левая колонка - название и длина */}
|
{/* Левая колонка - название и длина */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-bold text-l">
|
<h3 className="font-bold text-l">{yacht.name}</h3>
|
||||||
{yacht.name}
|
|
||||||
</h3>
|
|
||||||
<div className="flex items-center gap-1 text-sm">
|
<div className="flex items-center gap-1 text-sm">
|
||||||
<Icon size={16} name="width" />
|
<Icon size={16} name="width" />
|
||||||
<span>{formatWidth(yacht.length)}</span>
|
<span>{formatWidth(yacht.length)}</span>
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,99 @@ import { Button } from "@/components/ui/button";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { User, ArrowUpRight, Map, ArrowLeft, Heart } from "lucide-react";
|
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 { 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() {
|
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 [promocode, setPromocode] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Извлекаем параметры из URL
|
// Извлекаем параметры из URL
|
||||||
const yachtId = searchParams.get("yachtId");
|
const yachtId = searchParams.get("yachtId");
|
||||||
const guestCount = searchParams.get("guestCount");
|
const guestCount = searchParams.get("guests");
|
||||||
const departureDate = searchParams.get("departureDate");
|
const departureDate = searchParams.get("departureDate");
|
||||||
const departureTime = searchParams.get("departureTime");
|
const departureTime = searchParams.get("departureTime");
|
||||||
const arrivalDate = searchParams.get("arrivalDate");
|
const arrivalDate = searchParams.get("arrivalDate");
|
||||||
const arrivalTime = searchParams.get("arrivalTime");
|
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) => {
|
const formatDate = (dateString: string | null) => {
|
||||||
if (!dateString) return null;
|
if (!dateString) return null;
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateString);
|
const date = parseISO(dateString);
|
||||||
const months = [
|
const months = [
|
||||||
"янв", "фев", "мар", "апр", "май", "июн",
|
"янв",
|
||||||
"июл", "авг", "сен", "окт", "ноя", "дек"
|
"фев",
|
||||||
|
"мар",
|
||||||
|
"апр",
|
||||||
|
"май",
|
||||||
|
"июн",
|
||||||
|
"июл",
|
||||||
|
"авг",
|
||||||
|
"сен",
|
||||||
|
"окт",
|
||||||
|
"ноя",
|
||||||
|
"дек",
|
||||||
];
|
];
|
||||||
const day = date.getDate();
|
const day = date.getDate();
|
||||||
const month = months[date.getMonth()];
|
const month = months[date.getMonth()];
|
||||||
|
|
@ -41,10 +110,20 @@ export default function ConfirmPage() {
|
||||||
const formatDateFull = (dateString: string | null) => {
|
const formatDateFull = (dateString: string | null) => {
|
||||||
if (!dateString) return null;
|
if (!dateString) return null;
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateString);
|
const date = parseISO(dateString);
|
||||||
const months = [
|
const months = [
|
||||||
"января", "февраля", "марта", "апреля", "мая", "июня",
|
"января",
|
||||||
"июля", "августа", "сентября", "октября", "ноября", "декабря"
|
"февраля",
|
||||||
|
"марта",
|
||||||
|
"апреля",
|
||||||
|
"мая",
|
||||||
|
"июня",
|
||||||
|
"июля",
|
||||||
|
"августа",
|
||||||
|
"сентября",
|
||||||
|
"октября",
|
||||||
|
"ноября",
|
||||||
|
"декабря",
|
||||||
];
|
];
|
||||||
const day = date.getDate();
|
const day = date.getDate();
|
||||||
const month = months[date.getMonth()];
|
const month = months[date.getMonth()];
|
||||||
|
|
@ -57,7 +136,6 @@ export default function ConfirmPage() {
|
||||||
// Функция для форматирования времени
|
// Функция для форматирования времени
|
||||||
const formatTime = (timeString: string | null) => {
|
const formatTime = (timeString: string | null) => {
|
||||||
if (!timeString) return null;
|
if (!timeString) return null;
|
||||||
// Предполагаем формат HH:mm или HH:mm:ss
|
|
||||||
return timeString.split(":").slice(0, 2).join(":");
|
return timeString.split(":").slice(0, 2).join(":");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -72,22 +150,39 @@ export default function ConfirmPage() {
|
||||||
const arrivalDateFormattedFull = formatDateFull(arrivalDate);
|
const arrivalDateFormattedFull = formatDateFull(arrivalDate);
|
||||||
|
|
||||||
// Формируем строки для отображения
|
// Формируем строки для отображения
|
||||||
const departureDisplay = departureDateFormatted && departureTimeFormatted
|
const departureDisplay =
|
||||||
|
departureDateFormatted && departureTimeFormatted
|
||||||
? `${departureDateFormatted} ${departureTimeFormatted}`
|
? `${departureDateFormatted} ${departureTimeFormatted}`
|
||||||
: "Не выбрано";
|
: "Не выбрано";
|
||||||
|
|
||||||
const arrivalDisplay = arrivalDateFormatted && arrivalTimeFormatted
|
const arrivalDisplay =
|
||||||
|
arrivalDateFormatted && arrivalTimeFormatted
|
||||||
? `${arrivalDateFormatted} ${arrivalTimeFormatted}`
|
? `${arrivalDateFormatted} ${arrivalTimeFormatted}`
|
||||||
: "Не выбрано";
|
: "Не выбрано";
|
||||||
|
|
||||||
const datesDisplay = departureDateFormattedFull && departureTimeFormatted && arrivalDateFormattedFull && arrivalTimeFormatted
|
const datesDisplay =
|
||||||
|
departureDateFormattedFull &&
|
||||||
|
departureTimeFormatted &&
|
||||||
|
arrivalDateFormattedFull &&
|
||||||
|
arrivalTimeFormatted
|
||||||
? `${departureDateFormattedFull} в ${departureTimeFormatted} — ${arrivalDateFormattedFull} в ${arrivalTimeFormatted}`
|
? `${departureDateFormattedFull} в ${departureTimeFormatted} — ${arrivalDateFormattedFull} в ${arrivalTimeFormatted}`
|
||||||
: "Не выбрано";
|
: "Не выбрано";
|
||||||
|
|
||||||
const guestsDisplay = guestCount
|
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 (
|
return (
|
||||||
<main className="bg-[#f4f4f4] grow">
|
<main className="bg-[#f4f4f4] grow">
|
||||||
{/* Мобильная версия */}
|
{/* Мобильная версия */}
|
||||||
|
|
@ -101,16 +196,13 @@ export default function ConfirmPage() {
|
||||||
onClick={() => router.back()}
|
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"
|
className="flex-shrink-0 w-10 h-10 rounded-full border border-[#DFDFDF] flex items-center justify-center hover:bg-[#f4f4f4] transition-colors"
|
||||||
>
|
>
|
||||||
<ArrowLeft
|
<ArrowLeft size={20} className="text-[#333333]" />
|
||||||
size={20}
|
|
||||||
className="text-[#333333]"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Центральный блок с информацией */}
|
{/* Центральный блок с информацией */}
|
||||||
<div className="flex-1 min-w-0 text-center">
|
<div className="flex-1 min-w-0 text-center">
|
||||||
<h2 className="text-base font-bold text-[#333333] mb-1">
|
<h2 className="text-base font-bold text-[#333333] mb-1">
|
||||||
Яхта Сеньорита
|
Яхта {yacht.name}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex justify-center gap-10 text-xs text-[#666666]">
|
<div className="flex justify-center gap-10 text-xs text-[#666666]">
|
||||||
<span>{departureDateFormatted || "Не выбрано"}</span>
|
<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">
|
<button className="flex-shrink-0 w-10 h-10 flex items-center justify-center hover:opacity-70 transition-opacity">
|
||||||
<Heart
|
<Heart size={20} className="text-[#333333] stroke-2" />
|
||||||
size={20}
|
|
||||||
className="text-[#333333] stroke-2"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,9 +222,7 @@ export default function ConfirmPage() {
|
||||||
<div className="bg-white p-4">
|
<div className="bg-white p-4">
|
||||||
{/* Заголовок с иконкой */}
|
{/* Заголовок с иконкой */}
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<h1 className="text-xl text-[#333333]">
|
<h1 className="text-xl text-[#333333]">Ваше бронирование 🛥️</h1>
|
||||||
Ваше бронирование 🛥️
|
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Поля Выход и Заход */}
|
{/* Поля Выход и Заход */}
|
||||||
|
|
@ -145,9 +232,7 @@ export default function ConfirmPage() {
|
||||||
Выход
|
Выход
|
||||||
</label>
|
</label>
|
||||||
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
|
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
|
||||||
<div className="text-[#333333]">
|
<div className="text-[#333333]">{departureDisplay}</div>
|
||||||
{departureDisplay}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -155,9 +240,7 @@ export default function ConfirmPage() {
|
||||||
Заход
|
Заход
|
||||||
</label>
|
</label>
|
||||||
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
|
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF]">
|
||||||
<div className="text-[#333333]">
|
<div className="text-[#333333]">{arrivalDisplay}</div>
|
||||||
{arrivalDisplay}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -175,9 +258,7 @@ export default function ConfirmPage() {
|
||||||
Гостей
|
Гостей
|
||||||
</label>
|
</label>
|
||||||
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF] flex items-center justify-between">
|
<div className="bg-white rounded-full px-8 py-4 border border-[#DFDFDF] flex items-center justify-between">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">{guestsDisplay}</span>
|
||||||
{guestsDisplay}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -188,8 +269,7 @@ export default function ConfirmPage() {
|
||||||
Правила отмены
|
Правила отмены
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-base text-[#333333]">
|
<p className="text-base text-[#333333]">
|
||||||
При отмене до 10 мая вы получите частичный
|
При отмене до 10 мая вы получите частичный возврат.{" "}
|
||||||
возврат.{" "}
|
|
||||||
<Link
|
<Link
|
||||||
href="#"
|
href="#"
|
||||||
className="text-sm text-[#2D908D] hover:text-[#007088] font-bold transition-colors"
|
className="text-sm text-[#2D908D] hover:text-[#007088] font-bold transition-colors"
|
||||||
|
|
@ -205,28 +285,32 @@ export default function ConfirmPage() {
|
||||||
Детализация цены
|
Детализация цены
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
|
{totalHours > 0 && yacht.minCost ? (
|
||||||
|
<>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">
|
||||||
26 400₽ x 2ч
|
{formatPrice(yacht.minCost)}₽ × {totalHours}ч
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">
|
||||||
52 800 ₽
|
{formatPrice(totalPrice)} ₽
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center mb-4 pb-4 border-b border-[#DFDFDF]">
|
<div className="flex justify-between items-center mb-4 pb-4 border-b border-[#DFDFDF]">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">Услуги</span>
|
||||||
Услуги
|
|
||||||
</span>
|
|
||||||
<span className="text-[#333333]">0 Р</span>
|
<span className="text-[#333333]">0 Р</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">Итого:</span>
|
||||||
Итого:
|
|
||||||
</span>
|
|
||||||
<span className="font-bold text-[#333333]">
|
<span className="font-bold text-[#333333]">
|
||||||
52 800 Р
|
{formatPrice(totalPrice)} Р
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-[#999999] text-center py-4">
|
||||||
|
Укажите даты для расчета стоимости
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -237,9 +321,7 @@ export default function ConfirmPage() {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Промокод"
|
placeholder="Промокод"
|
||||||
value={promocode}
|
value={promocode}
|
||||||
onChange={(e) =>
|
onChange={(e) => setPromocode(e.target.value)}
|
||||||
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"
|
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
|
<Button
|
||||||
|
|
@ -255,6 +337,7 @@ export default function ConfirmPage() {
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200"
|
className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200"
|
||||||
|
disabled={totalHours === 0}
|
||||||
>
|
>
|
||||||
Отправить заявку
|
Отправить заявку
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -273,9 +356,7 @@ export default function ConfirmPage() {
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span>></span>
|
<span>></span>
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">Ваше бронирование</span>
|
||||||
Ваше бронирование
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row gap-6">
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
|
|
@ -286,7 +367,7 @@ export default function ConfirmPage() {
|
||||||
{/* Изображение яхты */}
|
{/* Изображение яхты */}
|
||||||
<div className="relative mb-5">
|
<div className="relative mb-5">
|
||||||
<Image
|
<Image
|
||||||
src="/images/yachts/yacht1.jpg"
|
src={getImageUrl(yacht.mainImageUrl)}
|
||||||
alt="Яхта"
|
alt="Яхта"
|
||||||
width={400}
|
width={400}
|
||||||
height={250}
|
height={250}
|
||||||
|
|
@ -295,16 +376,11 @@ export default function ConfirmPage() {
|
||||||
{/* Плашка владельца */}
|
{/* Плашка владельца */}
|
||||||
<div className="absolute top-2 left-2">
|
<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">
|
<div className="bg-white backdrop-blur-sm px-4 py-2 rounded-[8px] flex items-center gap-2">
|
||||||
<User
|
<User size={22} className="text-[#999999]" />
|
||||||
size={22}
|
|
||||||
className="text-[#999999]"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-[4px]">
|
<div className="flex flex-col gap-[4px]">
|
||||||
<span className="text-[#999999]">
|
<span className="text-[#999999]">Владелец</span>
|
||||||
Владелец
|
|
||||||
</span>
|
|
||||||
<span className="text-[#333333] font-bold">
|
<span className="text-[#333333] font-bold">
|
||||||
Денис
|
{yacht.owner.firstName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -312,7 +388,7 @@ export default function ConfirmPage() {
|
||||||
</div>
|
</div>
|
||||||
{/* Название яхты */}
|
{/* Название яхты */}
|
||||||
<h3 className="text-base text-[#333333] pb-3 border-b border-[#DFDFDF] mb-4">
|
<h3 className="text-base text-[#333333] pb-3 border-b border-[#DFDFDF] mb-4">
|
||||||
Яхта
|
Яхта {yacht.name}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Детализация цены */}
|
{/* Детализация цены */}
|
||||||
|
|
@ -321,30 +397,32 @@ export default function ConfirmPage() {
|
||||||
Детализация цены
|
Детализация цены
|
||||||
</h4>
|
</h4>
|
||||||
<div>
|
<div>
|
||||||
|
{totalHours > 0 && yacht.minCost ? (
|
||||||
|
<>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">
|
||||||
26 400₽ x 2ч
|
{formatPrice(yacht.minCost)}₽ × {totalHours}ч
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">
|
||||||
52 800 ₽
|
{formatPrice(totalPrice)} ₽
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center border-b border-[#DFDFDF] pb-4 mb-4">
|
<div className="flex justify-between items-center border-b border-[#DFDFDF] pb-4 mb-4">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">Услуги</span>
|
||||||
Услуги
|
<span className="text-[#333333]">0 Р</span>
|
||||||
</span>
|
|
||||||
<span className="text-[#333333]">
|
|
||||||
0 Р
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">Итого:</span>
|
||||||
Итого:
|
|
||||||
</span>
|
|
||||||
<span className="text-[#333333] font-bold">
|
<span className="text-[#333333] font-bold">
|
||||||
52 800 Р
|
{formatPrice(totalPrice)} Р
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-[#999999] text-center py-4">
|
||||||
|
Укажите даты для расчета стоимости
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -358,9 +436,7 @@ export default function ConfirmPage() {
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Промокод"
|
placeholder="Промокод"
|
||||||
value={promocode}
|
value={promocode}
|
||||||
onChange={(e) =>
|
onChange={(e) => setPromocode(e.target.value)}
|
||||||
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"
|
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
|
<Button
|
||||||
|
|
@ -385,13 +461,11 @@ export default function ConfirmPage() {
|
||||||
Ваше бронирование
|
Ваше бронирование
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Сведения о бронировании */}
|
{/* Сведения о бронирования */}
|
||||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||||
{/* Даты */}
|
{/* Даты */}
|
||||||
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
|
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
|
||||||
<div className="text-[#333333] mb-1">
|
<div className="text-[#333333] mb-1">Даты</div>
|
||||||
Даты
|
|
||||||
</div>
|
|
||||||
<div className="text-base text-[#999999]">
|
<div className="text-base text-[#999999]">
|
||||||
{datesDisplay}
|
{datesDisplay}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -400,9 +474,7 @@ export default function ConfirmPage() {
|
||||||
{/* Гости */}
|
{/* Гости */}
|
||||||
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
|
<div className="grow-1 border border-[#DFDFDF] rounded-[8px] p-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[#333333] mb-1">
|
<div className="text-[#333333] mb-1">Гости</div>
|
||||||
Гости
|
|
||||||
</div>
|
|
||||||
<div className="text-base text-[#999999]">
|
<div className="text-base text-[#999999]">
|
||||||
{guestsDisplay}
|
{guestsDisplay}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -423,8 +495,7 @@ export default function ConfirmPage() {
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p className="text-[#333333]">
|
<p className="text-[#333333]">
|
||||||
При отмене до 10 мая вы получите частичный
|
При отмене до 10 мая вы получите частичный возврат.
|
||||||
возврат.
|
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="#"
|
href="#"
|
||||||
|
|
@ -445,6 +516,7 @@ export default function ConfirmPage() {
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
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"
|
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>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,7 @@ const CommonPopoverContent: React.FC<CommonPopoverContentProps> = ({
|
||||||
onSelect={setDate}
|
onSelect={setDate}
|
||||||
className="mb-[24px]"
|
className="mb-[24px]"
|
||||||
locale={ru}
|
locale={ru}
|
||||||
disabled={(date) =>
|
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
||||||
date < new Date(new Date().setHours(0, 0, 0, 0))
|
|
||||||
}
|
|
||||||
classNames={{
|
classNames={{
|
||||||
root: "w-full",
|
root: "w-full",
|
||||||
month: "flex w-full flex-col gap-4",
|
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",
|
"flex h-8 w-full items-center justify-center px-8 text-gray-700 font-semibold",
|
||||||
table: "w-full border-collapse",
|
table: "w-full border-collapse",
|
||||||
weekdays: "flex",
|
weekdays: "flex",
|
||||||
weekday:
|
weekday: "flex-1 text-gray-500 text-xs font-normal p-2 text-center",
|
||||||
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
|
|
||||||
day_button: "font-bold ring-0 focus:ring-0",
|
day_button: "font-bold ring-0 focus:ring-0",
|
||||||
week: "mt-2 flex w-full",
|
week: "mt-2 flex w-full",
|
||||||
today: "bg-gray-100 text-gray-900 rounded-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">
|
<div className="mb-[24px] flex gap-3">
|
||||||
<Counter
|
<Counter
|
||||||
|
|
@ -217,10 +213,7 @@ export const GuestDatePicker: React.FC<GuestDatePickerProps> = ({
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{/* Кнопка Выход */}
|
{/* Кнопка Выход */}
|
||||||
<Popover
|
<Popover open={isDepartureOpen} onOpenChange={setIsDepartureOpen}>
|
||||||
open={isDepartureOpen}
|
|
||||||
onOpenChange={setIsDepartureOpen}
|
|
||||||
>
|
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ interface DatePickerProps {
|
||||||
onDateChange?: (date: Date | undefined) => void;
|
onDateChange?: (date: Date | undefined) => void;
|
||||||
onDepartureTimeChange?: (time: string) => void;
|
onDepartureTimeChange?: (time: string) => void;
|
||||||
onArrivalTimeChange?: (time: string) => void;
|
onArrivalTimeChange?: (time: string) => void;
|
||||||
|
onlyDeparture?: boolean;
|
||||||
|
onlyArrival?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DatePicker({
|
export function DatePicker({
|
||||||
|
|
@ -36,19 +38,31 @@ export function DatePicker({
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onDepartureTimeChange,
|
onDepartureTimeChange,
|
||||||
onArrivalTimeChange,
|
onArrivalTimeChange,
|
||||||
|
onlyDeparture,
|
||||||
|
onlyArrival,
|
||||||
}: DatePickerProps) {
|
}: DatePickerProps) {
|
||||||
const [internalDate, setInternalDate] = React.useState<Date>();
|
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 [internalArrivalTime, setInternalArrivalTime] = React.useState("13:00");
|
||||||
const [open, setOpen] = React.useState(false);
|
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 date = value !== undefined ? value || undefined : internalDate;
|
||||||
const departureTime = externalDepartureTime !== undefined ? externalDepartureTime : internalDepartureTime;
|
const departureTime =
|
||||||
const arrivalTime = externalArrivalTime !== undefined ? externalArrivalTime : internalArrivalTime;
|
externalDepartureTime !== undefined
|
||||||
|
? externalDepartureTime
|
||||||
|
: internalDepartureTime;
|
||||||
|
const arrivalTime =
|
||||||
|
externalArrivalTime !== undefined
|
||||||
|
? externalArrivalTime
|
||||||
|
: internalArrivalTime;
|
||||||
|
|
||||||
const handleDateChange = (newDate: Date | undefined) => {
|
const handleDateChange = (newDate: Date | undefined) => {
|
||||||
if (onDateChange) {
|
if (onDateChange) {
|
||||||
|
|
@ -91,17 +105,12 @@ export function DatePicker({
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{showIcon && (
|
{showIcon && (
|
||||||
<Icon
|
<Icon name="calendar" className="w-4 h-4 text-brand mr-2" />
|
||||||
name="calendar"
|
|
||||||
className="w-4 h-4 text-brand mr-2"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{date ? (
|
{date ? (
|
||||||
format(
|
format(date, `d MMMM, ${departureTime} - ${arrivalTime}`, {
|
||||||
date,
|
locale: ru,
|
||||||
`d MMMM, ${departureTime} - ${arrivalTime}`,
|
})
|
||||||
{ locale: ru }
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<span>{placeholder}</span>
|
<span>{placeholder}</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -150,6 +159,7 @@ export function DatePicker({
|
||||||
|
|
||||||
{/* Поля времени */}
|
{/* Поля времени */}
|
||||||
<div className="flex gap-3 mb-4">
|
<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">
|
<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 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
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={departureTime}
|
value={departureTime}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleDepartureTimeChange(e.target.value)}
|
||||||
handleDepartureTimeChange(e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full focus:outline-none focus:border-transparent"
|
className="w-full focus:outline-none focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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 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
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
value={arrivalTime}
|
value={arrivalTime}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleArrivalTimeChange(e.target.value)}
|
||||||
handleArrivalTimeChange(e.target.value)
|
|
||||||
}
|
|
||||||
className="w-full focus:outline-none focus:border-transparent"
|
className="w-full focus:outline-none focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопка Применить */}
|
{/* Кнопка Применить */}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const useApiClient = () => {
|
||||||
const authPopup = useAuthPopup();
|
const authPopup = useAuthPopup();
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: "http://192.168.1.5:4000/",
|
baseURL: "/api",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue