import { SelectInputProps } from "@/components/ui/SelectInput/SelectInput"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; // Валидация даты без зависимости от часовых зон // Проверяет что дата существует (например, 31 февраля - невалидна) const isValidDate = (year: number, month: number, day: number) => { if (!year || !month || !day) return false; // Используем Date в локальной зоне только для валидации // Это безопасно, т.к. мы не сохраняем результат, только проверяем корректность 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 }; }; // Порядок полей даты: месяц-день-год (американский формат) // Этот формат используется для всех локалей чтобы обеспечить единообразие const getDateInputLocaleFormat = (): ("d" | "m" | "y")[] => { // Всегда используем американский формат: месяц, день, год return ["m", "d", "y"]; }; interface UseDateInputProps { value?: string | null; onChange?: (value: string | null) => void; maxYear?: number; yearsRange?: number; } export const useDateInput = ({ value, onChange, maxYear = new Date().getFullYear() - 11, yearsRange = 100, }: UseDateInputProps) => { const [year, setYear] = useState(""); const [month, setMonth] = useState(""); const [day, setDay] = useState(""); const lastEmittedValue = useRef(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(() => { const monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; return Array.from({ length: 12 }, (_, i) => ({ value: String(i + 1).padStart(2, "0"), label: monthNames[i], })); }, []); 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(), [] ); 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, }; };