Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/ui/input/InputDatePicker.tsx
T

284 lines
8.5 KiB
TypeScript

import * as React from "react";
import { t as i18nT } from "i18next";
import { parse } from "date-fns";
import { CalendarIcon, ChevronDownIcon, XIcon } from "~/components/ui/icons/vendor";
import { Button } from "../button";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
import { cn } from "~/lib/utils";
import { Calendar } from "../calendar";
import { Input } from "../input";
import { Tabs, TabsContent } from "../tabs";
import { useLocaleFormatters } from "~/locale/format";
type Props = {
name?: string;
date: Date | undefined | null;
setDate: (date: Date | undefined) => void;
dirty?: boolean;
canClear?: boolean;
className?: string;
closeOnSelect?: boolean;
showInput?: boolean;
placeholder?: string;
required?: boolean;
disabled?: boolean;
autoFocus?: boolean;
style?: React.CSSProperties;
fromYear?: number;
toYear?: number;
};
export function InputDatePickerDeprecated({
name,
date,
setDate,
dirty,
canClear = true,
className,
closeOnSelect = false,
showInput = false,
placeholder,
required,
disabled,
autoFocus,
style,
fromYear,
toYear,
}: Props) {
const formatters = useLocaleFormatters();
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");
const [activeTab, setActiveTab] = React.useState("calendar");
const [month, setMonth] = React.useState<Date | undefined>(date || undefined);
React.useEffect(() => {
if (date) {
try {
const parsedDate = new Date(date);
if (!isNaN(parsedDate.getTime())) {
setInputValue(parsedDate.toISOString().split("T")[0]);
}
} catch (e) {
console.error("Invalid date:", date);
}
} else {
setInputValue("");
}
}, [date]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
if (value) {
// Parse the date string in local timezone
const [year, month, day] = value.split("-").map(Number);
const newDate = new Date(year, month - 1, day);
if (!isNaN(newDate.getTime())) {
setDate(newDate);
}
} else {
setDate(undefined);
}
};
const displayDate = React.useMemo(() => {
if (!date) return null;
try {
const parsedDate = new Date(date);
if (!isNaN(parsedDate.getTime())) {
return formatters.date(parsedDate, { dateStyle: "long" });
}
} catch (e) {
console.error("Invalid date:", date);
}
return null;
}, [date, formatters]);
return (
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
{name && <input type="hidden" name={name} value={date ? new Date(date).toISOString() : ""} readOnly hidden />}
<PopoverTrigger asChild>
<div className="relative truncate">
<Button
type="button"
variant={"outline"}
className={cn("w-full justify-start truncate text-left font-normal", !date && "text-muted-foreground", className)}
disabled={disabled}
autoFocus={autoFocus}
style={style}
>
<CalendarIcon className="size-3" />
{displayDate || <span>{i18nT("components.date.pick")}</span>}
</Button>
{canClear && date && (
<div className="absolute top-1 right-1">
<Button
type="button"
variant={"ghost"}
size="xs"
className="text-muted-foreground"
onClick={(e) => {
e.stopPropagation();
setDate(undefined);
}}
>
<XIcon className="size-4" />
</Button>
</div>
)}
</div>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full gap-0">
<TabsContent value="calendar" className="w-[250px] p-0">
<div className="px-3 pt-3">
<Input
type="date"
value={inputValue}
onChange={handleInputChange}
className="mb-2"
required={required}
disabled={disabled}
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsCalendarOpen(false);
}
}}
/>
</div>
<Calendar
mode="single"
selected={date ? new Date(date) : undefined}
month={month || date || undefined}
onMonthChange={(e) => {
setMonth(e);
}}
onSelect={(e: Date | undefined) => {
setDate(e);
if (closeOnSelect) {
setIsCalendarOpen(false);
}
}}
required={required}
disabled={disabled}
fromYear={fromYear}
toYear={toYear}
/>
</TabsContent>
<TabsContent value="manual" className="w-[250px] p-3">
<Input
type="date"
value={inputValue}
onChange={handleInputChange}
className="mb-2"
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsCalendarOpen(false);
}
}}
/>
</TabsContent>
</Tabs>
</PopoverContent>
</Popover>
);
}
export default function InputDatePicker(props: Props) {
const formatters = useLocaleFormatters();
const [open, setOpen] = React.useState(false);
const [date, setDate] = React.useState<Date | undefined | null>(props.date ? new Date(props.date) : undefined);
return (
<div className="flex flex-col gap-3">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
id="date"
className={cn("justify-between bg-transparent font-normal hover:bg-transparent", props.className)}
dirty={props.dirty}
disabled={props.disabled}
style={props.style}
>
{date ? formatters.date(date, { dateStyle: "long" }) : props.placeholder || i18nT("components.date.select")}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent className="bg-background w-auto overflow-hidden p-0" align="start">
<Calendar
mode="single"
selected={date || undefined}
captionLayout="dropdown"
onSelect={(date) => {
setDate(date);
setOpen(false);
props.setDate(date);
}}
fromYear={props.fromYear}
toYear={props.toYear}
/>
{/* <div className="px-2 pb-2">
<ManualInputDatePicker date={date} onChange={setDate} />
</div> */}
<div className="flex items-center justify-center gap-2 px-3 pb-2">
{/* Today button */}
<Button
variant="outline"
size="sm"
onClick={() => {
setDate(new Date());
props.setDate(new Date());
}}
className="w-1/2"
>
{i18nT("shared.dateRange.today")}
</Button>
{/* Clear button */}
<Button
variant="outline"
size="sm"
onClick={() => {
setDate(null);
props.setDate(undefined);
}}
disabled={!date}
className="w-1/2"
>
{i18nT("shared.clear")}
</Button>
</div>
</PopoverContent>
</Popover>
</div>
);
}
// function ManualInputDatePicker({
// date,
// onChange,
// inputFormat = "mm/dd/yyyy",
// }: {
// date: Date | undefined;
// onChange: (date: Date | undefined) => void;
// inputFormat?: string;
// }) {
// const [inputValue, setInputValue] = React.useState(date ? format(date, inputFormat) : "");
// function tryParseDate(value: string) {
// setInputValue(value);
// try {
// const parsedDate = parse(value, inputFormat, new Date());
// if (!isNaN(parsedDate.getTime())) {
// onChange(parsedDate);
// }
// } catch (e) {
// console.error("Invalid date:", value);
// }
// }
// return <Input value={inputValue} onChange={(e) => tryParseDate(e.target.value)} placeholder={inputFormat} />;
// }