w-funnel/src/hooks/useDateInput.ts
gofnnp 8101de209b develop
new components
2025-09-27 21:20:18 +04:00

163 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
};