186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
import clsx from "clsx";
|
|
import { forwardRef, Fragment, ReactNode, Ref, RefObject, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
import EntityIcon from "~/components/layouts/icons/EntityIcon";
|
|
import HintTooltip from "~/components/ui/tooltips/HintTooltip";
|
|
import { Calendar as CalendarIcon } from "~/components/ui/icons/vendor";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
|
import { Button } from "../button";
|
|
import { Calendar } from "../calendar";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Input } from "../input";
|
|
import { cn } from "~/lib/utils";
|
|
import { useLocaleFormatters } from "~/locale/format";
|
|
|
|
export interface RefInputDate {
|
|
input: RefObject<HTMLInputElement | null>;
|
|
}
|
|
|
|
interface Props {
|
|
display?: "default" | "calendar";
|
|
name?: string;
|
|
title: string;
|
|
defaultValue?: Date | undefined;
|
|
value?: Date;
|
|
onChange?: (date: Date) => void;
|
|
className?: string;
|
|
help?: string;
|
|
disabled?: boolean;
|
|
readOnly?: boolean;
|
|
required?: boolean;
|
|
hint?: ReactNode;
|
|
icon?: string;
|
|
darkMode?: boolean;
|
|
autoFocus?: boolean;
|
|
classNameInput?: string;
|
|
}
|
|
const InputDate = (
|
|
{
|
|
display = "default",
|
|
name,
|
|
title,
|
|
value,
|
|
defaultValue,
|
|
onChange,
|
|
className,
|
|
help,
|
|
disabled = false,
|
|
readOnly = false,
|
|
required = false,
|
|
hint,
|
|
icon,
|
|
darkMode,
|
|
autoFocus,
|
|
classNameInput,
|
|
}: Props,
|
|
ref: Ref<RefInputDate>
|
|
) => {
|
|
const { t } = useTranslation();
|
|
const formatters = useLocaleFormatters();
|
|
useImperativeHandle(ref, () => ({ input }));
|
|
const input = useRef<HTMLInputElement>(null);
|
|
|
|
const [actualValue, setActualValue] = useState<Date | undefined>(undefined);
|
|
|
|
useEffect(() => {
|
|
if (defaultValue) {
|
|
// setActualValue(defaultValue?.toISOString().split("T")[0]);
|
|
setActualValue(defaultValue);
|
|
}
|
|
}, [defaultValue]);
|
|
|
|
useEffect(() => {
|
|
if (value) {
|
|
const date = new Date(value);
|
|
if (date) {
|
|
// setActualValue(date.toISOString().split("T")[0]);
|
|
setActualValue(date);
|
|
}
|
|
}
|
|
}, [value]);
|
|
|
|
useEffect(() => {
|
|
if (defaultValue) {
|
|
const date = new Date(defaultValue);
|
|
if (date) {
|
|
// setActualValue(date.toISOString().split("T")[0]);
|
|
setActualValue(date);
|
|
}
|
|
}
|
|
}, [defaultValue]);
|
|
|
|
useEffect(() => {
|
|
if (onChange && actualValue) {
|
|
let isDifferent = false;
|
|
try {
|
|
if (value && actualValue) {
|
|
isDifferent = value.toISOString() !== actualValue.toISOString();
|
|
}
|
|
} catch {
|
|
isDifferent = true;
|
|
}
|
|
if (isDifferent) {
|
|
const date = new Date(actualValue);
|
|
if (date) {
|
|
onChange(date);
|
|
}
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [actualValue]);
|
|
|
|
return (
|
|
<div className={clsx(className, !darkMode && "")}>
|
|
<label htmlFor={name} className="flex justify-between space-x-2 text-xs font-medium">
|
|
<div className=" flex items-center space-x-1">
|
|
<div className="truncate">
|
|
{title}
|
|
{required && <span className="ml-1 text-red-600">*</span>}
|
|
</div>
|
|
|
|
{help && <HintTooltip text={help} />}
|
|
</div>
|
|
{hint}
|
|
</label>
|
|
<div className="relative mt-1 flex w-full rounded-md">
|
|
{icon && (
|
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
<EntityIcon className="h-5 w-5" icon={icon} />
|
|
</div>
|
|
)}
|
|
{display === "default" && (
|
|
<Input
|
|
ref={input}
|
|
type="date"
|
|
id={name}
|
|
name={name}
|
|
required={required}
|
|
value={actualValue?.toISOString().split("T")[0]}
|
|
onChange={(e) => {
|
|
const date = new Date(e.target.value);
|
|
if (date) {
|
|
setActualValue(date);
|
|
}
|
|
}}
|
|
disabled={disabled || readOnly}
|
|
readOnly={readOnly}
|
|
autoFocus={autoFocus}
|
|
className={cn((disabled || readOnly) && "bg-secondary/90 cursor-not-allowed", icon && "pl-10", classNameInput)}
|
|
/>
|
|
)}
|
|
{display === "calendar" && (
|
|
<Fragment>
|
|
{name && (
|
|
<input
|
|
type="hidden"
|
|
name={name}
|
|
value={actualValue?.toISOString().split("T")[0]}
|
|
required={required}
|
|
disabled={actualValue === undefined}
|
|
hidden
|
|
readOnly
|
|
/>
|
|
)}
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant={"outline"}
|
|
className={clsx("w-full justify-start text-left font-normal", !actualValue && "text-muted-foreground")}
|
|
disabled={disabled || readOnly}
|
|
autoFocus={autoFocus}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{actualValue ? formatters.date(actualValue) : t("components.date.pick")}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0">
|
|
<Calendar mode="single" selected={actualValue} onSelect={(date) => setActualValue(date)} />
|
|
</PopoverContent>
|
|
</Popover>
|
|
</Fragment>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
export default forwardRef(InputDate);
|