208 lines
4.7 KiB
TypeScript
208 lines
4.7 KiB
TypeScript
import { forwardRef, useMemo } from "react";
|
|
import DatePicker, { registerLocale } from "react-datepicker";
|
|
import { cs } from "date-fns/locale";
|
|
import { parse, format } from "date-fns";
|
|
import "react-datepicker/dist/react-datepicker.css";
|
|
|
|
registerLocale("cs", cs);
|
|
|
|
// Ensure portal root exists
|
|
if (
|
|
typeof document !== "undefined" &&
|
|
!document.getElementById("datepicker-portal")
|
|
) {
|
|
const el = document.createElement("div");
|
|
el.id = "datepicker-portal";
|
|
document.body.appendChild(el);
|
|
}
|
|
|
|
const isTouchDevice = () =>
|
|
typeof window !== "undefined" &&
|
|
("ontouchstart" in window || navigator.maxTouchPoints > 0);
|
|
|
|
interface CustomInputProps {
|
|
value?: string;
|
|
onClick?: () => void;
|
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
placeholder?: string;
|
|
required?: boolean;
|
|
readOnly?: boolean;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>(
|
|
(
|
|
{ value, onClick, onChange, placeholder, required, readOnly, disabled },
|
|
ref,
|
|
) => (
|
|
<input
|
|
className="admin-form-input"
|
|
onClick={onClick}
|
|
onChange={onChange}
|
|
value={value}
|
|
placeholder={placeholder}
|
|
ref={ref}
|
|
required={required}
|
|
readOnly={readOnly}
|
|
disabled={disabled}
|
|
autoComplete="off"
|
|
/>
|
|
),
|
|
);
|
|
|
|
interface NativeInputProps {
|
|
mode: string;
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
required?: boolean;
|
|
minDate?: string;
|
|
maxDate?: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
const modeToInputType: Record<string, string> = {
|
|
month: "month",
|
|
time: "time",
|
|
};
|
|
|
|
function NativeInput({
|
|
mode,
|
|
value,
|
|
onChange,
|
|
required,
|
|
minDate,
|
|
maxDate,
|
|
disabled,
|
|
}: NativeInputProps) {
|
|
const type = modeToInputType[mode] || "date";
|
|
return (
|
|
<input
|
|
type={type}
|
|
lang="cs"
|
|
value={value || ""}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
className="admin-form-input"
|
|
required={required}
|
|
disabled={disabled}
|
|
min={minDate || undefined}
|
|
max={maxDate || undefined}
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface AdminDatePickerProps {
|
|
mode?: "date" | "month" | "datetime" | "time";
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
minDate?: string;
|
|
maxDate?: string;
|
|
disabled?: boolean;
|
|
placeholder?: string;
|
|
required?: boolean;
|
|
}
|
|
|
|
export default function AdminDatePicker({
|
|
mode = "date",
|
|
value,
|
|
onChange,
|
|
required,
|
|
minDate,
|
|
maxDate,
|
|
disabled,
|
|
placeholder,
|
|
}: AdminDatePickerProps) {
|
|
const useNative = useMemo(() => isTouchDevice(), []);
|
|
|
|
if (useNative) {
|
|
return (
|
|
<NativeInput
|
|
mode={mode}
|
|
value={value}
|
|
onChange={onChange}
|
|
required={required}
|
|
minDate={minDate}
|
|
maxDate={maxDate}
|
|
disabled={disabled}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const toDate = (val: string | null | undefined): Date | null => {
|
|
if (!val) return null;
|
|
try {
|
|
if (mode === "date") return parse(val, "yyyy-MM-dd", new Date());
|
|
if (mode === "time") {
|
|
const [h, m] = val.split(":");
|
|
const d = new Date();
|
|
d.setHours(parseInt(h, 10), parseInt(m, 10), 0, 0);
|
|
return d;
|
|
}
|
|
if (mode === "month") return parse(val, "yyyy-MM", new Date());
|
|
} catch {
|
|
return null;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const handleChange = (date: Date | null) => {
|
|
if (!date) {
|
|
onChange("");
|
|
return;
|
|
}
|
|
if (mode === "date") onChange(format(date, "yyyy-MM-dd"));
|
|
else if (mode === "time") onChange(format(date, "HH:mm"));
|
|
else if (mode === "month") onChange(format(date, "yyyy-MM"));
|
|
};
|
|
|
|
const parseMinMax = (val: string | undefined): Date | undefined => {
|
|
if (!val) return undefined;
|
|
try {
|
|
if (mode === "date") return parse(val, "yyyy-MM-dd", new Date());
|
|
if (mode === "month") return parse(val, "yyyy-MM", new Date());
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const commonProps = {
|
|
selected: toDate(value),
|
|
onChange: handleChange,
|
|
locale: "cs",
|
|
customInput: (
|
|
<CustomInput
|
|
required={required}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
/>
|
|
),
|
|
minDate: parseMinMax(minDate),
|
|
maxDate: parseMinMax(maxDate),
|
|
popperPlacement: "bottom-start" as const,
|
|
portalId: "datepicker-portal",
|
|
disabled,
|
|
};
|
|
|
|
if (mode === "time") {
|
|
return (
|
|
<DatePicker
|
|
{...commonProps}
|
|
showTimeSelect
|
|
showTimeSelectOnly
|
|
timeIntervals={5}
|
|
timeCaption="Čas"
|
|
dateFormat="HH:mm"
|
|
timeFormat="HH:mm"
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (mode === "month") {
|
|
return (
|
|
<DatePicker {...commonProps} showMonthYearPicker dateFormat="MM/yyyy" />
|
|
);
|
|
}
|
|
|
|
return <DatePicker {...commonProps} dateFormat="dd.MM.yyyy" />;
|
|
}
|