163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import { SelectInputProps } from "@/components/ui/SelectInput/SelectInput";
|
||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||
|
||
const isValidDate = (year: number, month: number, day: number) => {
|
||
if (!year || !month || !day) return false;
|
||
const date = new Date(year, month - 1, day);
|
||
return (
|
||
date.getFullYear() === year &&
|
||
date.getMonth() === month - 1 &&
|
||
date.getDate() === day
|
||
);
|
||
};
|
||
|
||
const parseDateValue = (
|
||
value?: string | null
|
||
): { year: string; month: string; day: string } | null => {
|
||
if (!value) return null;
|
||
|
||
// Поддерживаем форматы: "2003-04-09" и "2003-04-09 12:00"
|
||
const dateMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:\s+\d{2}:\d{2})?$/);
|
||
|
||
if (!dateMatch) return null;
|
||
|
||
const [, year, month, day] = dateMatch;
|
||
return { year, month, day };
|
||
};
|
||
|
||
// Упрощенное определение порядка полей даты на основе локали.
|
||
// В реальном приложении здесь лучше использовать данные из next-intl.
|
||
const getDateInputLocaleFormat = (locale: string): ("d" | "m" | "y")[] => {
|
||
const format = new Intl.DateTimeFormat(locale).format(new Date(2001, 1, 3)); // Используем 3/Feb/2001
|
||
if (/^3.*2/.test(format)) return ["d", "m", "y"]; // 3/2/2001 -> d/m/y
|
||
if (/^2.*3/.test(format)) return ["m", "d", "y"]; // 2/3/2001 -> m/d/y
|
||
return ["y", "m", "d"]; // 2001/2/3 -> y/m/d
|
||
};
|
||
|
||
interface UseDateInputProps {
|
||
value?: string | null;
|
||
onChange?: (value: string | null) => void;
|
||
maxYear?: number;
|
||
yearsRange?: number;
|
||
locale?: string;
|
||
}
|
||
|
||
export const useDateInput = ({
|
||
value,
|
||
onChange,
|
||
maxYear = new Date().getFullYear() - 11,
|
||
yearsRange = 100,
|
||
locale = "en",
|
||
}: UseDateInputProps) => {
|
||
const [year, setYear] = useState("");
|
||
const [month, setMonth] = useState("");
|
||
const [day, setDay] = useState("");
|
||
|
||
const lastEmittedValue = useRef<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
const parsedDate = parseDateValue(value);
|
||
|
||
if (parsedDate) {
|
||
setYear(parsedDate.year);
|
||
setMonth(parsedDate.month);
|
||
setDay(parsedDate.day);
|
||
} else {
|
||
setYear("");
|
||
setMonth("");
|
||
setDay("");
|
||
}
|
||
}, [value]);
|
||
|
||
const updateValue = useCallback(
|
||
(newValue: string | null) => {
|
||
if (newValue !== lastEmittedValue.current) {
|
||
lastEmittedValue.current = newValue;
|
||
onChange?.(newValue);
|
||
}
|
||
},
|
||
[onChange]
|
||
);
|
||
|
||
useEffect(() => {
|
||
const numericYear = Number(year);
|
||
const numericMonth = Number(month);
|
||
const numericDay = Number(day);
|
||
|
||
if (isValidDate(numericYear, numericMonth, numericDay)) {
|
||
const formattedDate = `${year}-${month}-${day}`;
|
||
updateValue(formattedDate);
|
||
} else {
|
||
if (year || month || day) {
|
||
updateValue(null);
|
||
}
|
||
}
|
||
}, [year, month, day, updateValue]);
|
||
|
||
const yearOptions = useMemo(
|
||
() =>
|
||
Array.from({ length: yearsRange }, (_, i) => ({
|
||
value: maxYear - i,
|
||
label: String(maxYear - i),
|
||
})),
|
||
[maxYear, yearsRange]
|
||
);
|
||
|
||
const monthOptions = useMemo(
|
||
() =>
|
||
Array.from({ length: 12 }, (_, i) => ({
|
||
value: String(i + 1).padStart(2, "0"),
|
||
label: String(i + 1),
|
||
})),
|
||
[]
|
||
);
|
||
|
||
const dayOptions = useMemo(() => {
|
||
const daysInMonth =
|
||
year && month ? new Date(Number(year), Number(month), 0).getDate() : 31;
|
||
return Array.from({ length: daysInMonth }, (_, i) => ({
|
||
value: String(i + 1).padStart(2, "0"),
|
||
label: String(i + 1),
|
||
}));
|
||
}, [year, month]);
|
||
|
||
const localeFormat = useMemo(
|
||
() => getDateInputLocaleFormat(locale),
|
||
[locale]
|
||
);
|
||
|
||
const handleYearChange: SelectInputProps["onValueChange"] = useCallback(
|
||
(value: string) => {
|
||
setYear(value);
|
||
},
|
||
[]
|
||
);
|
||
|
||
const handleMonthChange: SelectInputProps["onValueChange"] = useCallback(
|
||
(value: string) => {
|
||
setMonth(value);
|
||
},
|
||
[]
|
||
);
|
||
|
||
const handleDayChange: SelectInputProps["onValueChange"] = useCallback(
|
||
(value: string) => {
|
||
setDay(value);
|
||
},
|
||
[]
|
||
);
|
||
|
||
return {
|
||
year,
|
||
month,
|
||
day,
|
||
yearOptions,
|
||
monthOptions,
|
||
dayOptions,
|
||
localeFormat,
|
||
handleYearChange,
|
||
handleMonthChange,
|
||
handleDayChange,
|
||
};
|
||
};
|