travelmarine-frontend/src/components/ui/date-picker.tsx

201 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import * as React from "react";
import { format } from "date-fns";
import { ru } from "date-fns/locale";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import Icon from "./icon";
interface DatePickerProps {
showIcon?: boolean;
variant?: "default" | "small";
placeholder?: string;
value?: Date | null;
departureTime?: string;
arrivalTime?: string;
onDateChange?: (date: Date | undefined) => void;
onDepartureTimeChange?: (time: string) => void;
onArrivalTimeChange?: (time: string) => void;
}
export function DatePicker({
showIcon = true,
variant = "default",
placeholder = "Выберите дату и время",
value,
departureTime: externalDepartureTime,
arrivalTime: externalArrivalTime,
onDateChange,
onDepartureTimeChange,
onArrivalTimeChange,
}: DatePickerProps) {
const [internalDate, setInternalDate] = React.useState<Date>();
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 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) {
onDateChange(newDate);
} else if (!isControlled) {
setInternalDate(newDate);
}
};
const handleDepartureTimeChange = (time: string) => {
if (onDepartureTimeChange) {
onDepartureTimeChange(time);
} else if (!isControlled) {
setInternalDepartureTime(time);
}
};
const handleArrivalTimeChange = (time: string) => {
if (onArrivalTimeChange) {
onArrivalTimeChange(time);
} else if (!isControlled) {
setInternalArrivalTime(time);
}
};
const handleApply = () => {
// Закрываем popover после применения
setOpen(false);
};
const heightClass = variant === "small" ? "h-[48px]" : "h-[64px]";
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
data-empty={!date}
className={`w-full ${heightClass} justify-between text-left font-normal`}
>
<div className="flex items-center">
{showIcon && (
<Icon
name="calendar"
className="w-4 h-4 text-brand mr-2"
/>
)}
{date ? (
format(
date,
`d MMMM, ${departureTime} - ${arrivalTime}`,
{ locale: ru }
)
) : (
<span>{placeholder}</span>
)}
</div>
{open ? (
<ChevronUpIcon className="w-4 h-4" />
) : (
<ChevronDownIcon className="w-4 h-4" />
)}
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 bg-white rounded-[20px] shadow-lg">
<div className="p-4 w-full">
{/* Календарь */}
<Calendar
mode="single"
selected={date}
onSelect={handleDateChange}
className="mb-4 "
locale={ru}
disabled={(date) =>
date < new Date(new Date().setHours(0, 0, 0, 0))
}
classNames={{
root: "w-full",
month: "flex w-full flex-col gap-4",
button_previous:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
button_next:
"h-8 w-8 flex items-center justify-center hover:bg-gray-100 rounded-md",
month_caption:
"flex h-8 w-full items-center justify-center px-8 text-gray-700 font-semibold",
table: "w-full border-collapse",
weekdays: "flex",
weekday:
"flex-1 text-gray-500 text-xs font-normal p-2 text-center",
day_button: "font-bold ring-0 focus:ring-0",
week: "mt-2 flex w-full",
today: "bg-gray-100 text-gray-900 rounded-full",
outside: "text-gray-300",
disabled: "text-gray-400 cursor-not-allowed",
selected:
"rounded-full border-none outline-none !bg-brand text-white",
}}
/>
{/* Поля времени */}
<div className="flex gap-3 mb-4">
<div className="relative w-full h-12 px-3 border border-gray-300 rounded-full text-gray-700 font-medium text-center">
<label className="absolute left-[24px] top-0 transform -translate-y-1/2 text-xs text-gray-500 pointer-events-none transition-all duration-200 bg-white px-1">
Выход
</label>
<div className="relative h-full flex align-center">
<ChevronDownIcon className="absolute right-0 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
<input
type="time"
value={departureTime}
onChange={(e) =>
handleDepartureTimeChange(e.target.value)
}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
<div className="relative w-full h-12 px-3 border border-gray-300 rounded-full text-gray-700 font-medium text-center">
<label className="absolute left-[24px] top-0 transform -translate-y-1/2 text-xs text-gray-500 pointer-events-none transition-all duration-200 bg-white px-1">
Заход
</label>
<div className="relative h-full flex align-center">
<ChevronDownIcon className="absolute right-0 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500 pointer-events-none" />
<input
type="time"
value={arrivalTime}
onChange={(e) =>
handleArrivalTimeChange(e.target.value)
}
className="w-full focus:outline-none focus:border-transparent"
/>
</div>
</div>
</div>
{/* Кнопка Применить */}
<Button
onClick={handleApply}
variant="gradient"
className="font-bold text-white h-[44px] w-full px-8"
>
Применить
</Button>
</div>
</PopoverContent>
</Popover>
);
}