diff --git a/src/components/ui/DatePicker.tsx b/src/components/ui/DatePicker.tsx index d6c7f8f..d46f8cd 100644 --- a/src/components/ui/DatePicker.tsx +++ b/src/components/ui/DatePicker.tsx @@ -1,289 +1,355 @@ "use client"; -import { useState, useRef, useEffect, useCallback } from "react"; -import { createPortal } from "react-dom"; -import { FiCalendar, FiChevronLeft, FiChevronRight, FiX } from "react-icons/fi"; -interface DatePickerProps { +import React, { useMemo } from "react"; +import { + DatePicker as HeroUIDatePicker, + DateField as HeroUIDateField, + Calendar as HeroUICalendar, + Label, + FieldError, +} from "@heroui/react"; + +import type { DateValue } from "@internationalized/date"; +import { parseDate as parseInternationalDate } from "@internationalized/date"; + +import { + FiCalendar, + FiChevronLeft, + FiChevronRight, + FiX, +} from "react-icons/fi"; + +export interface DatePickerProps { label?: string; - value?: string; // "YYYY-MM-DD" - onChange: (value: string | undefined) => void; + value?: string; //Fecha en formato YYYY-MM-DD o undefined cuando se limpia + onChange: (value: string | undefined) => void; //Devuelve la fecha en formato YYYY-MM-DD o undefined cuando se limpia. placeholder?: string; required?: boolean; - maxDate?: Date; + maxDate?: Date; //Fecha máxima permitida. + className?: string; + errorMessage?: string; } -const MONTHS = [ - "Enero","Febrero","Marzo","Abril","Mayo","Junio", - "Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre", -]; -const DAYS = ["Do","Lu","Ma","Mi","Ju","Vi","Sa"]; -const DROPDOWN_H = 320; // px estimado del dropdown +function toCalendarDate(dateStr?: string): DateValue | undefined { + if (!dateStr) { + return undefined; + } -function parseDate(value?: string): Date | undefined { - if (!value) return undefined; - const d = new Date(value + "T12:00:00"); - return isNaN(d.getTime()) ? undefined : d; + try { + return parseInternationalDate(dateStr); + } catch { + return undefined; + } } -function formatDisplay(value?: string): string { - const d = parseDate(value); - if (!d) return ""; - return d.toLocaleDateString("es-MX", { day: "numeric", month: "long", year: "numeric" }); +function toDateString(date: DateValue): string { + const yyyy = date.year; + const mm = String(date.month).padStart(2, "0"); + const dd = String(date.day).padStart(2, "0"); + + return `${yyyy}-${mm}-${dd}`; } -export function DatePicker({ - label, - value, - onChange, - placeholder = "Selecciona una fecha", - required, - maxDate = new Date(), -}: DatePickerProps) { - const selected = parseDate(value); - const [open, setOpen] = useState(false); - const [view, setView] = useState<"days" | "months" | "years">("days"); - const [cursor, setCursor] = useState(() => { - const d = selected ?? new Date(); - return { month: d.getMonth(), year: d.getFullYear() }; - }); - const [pos, setPos] = useState({ top: 0, left: 0, width: 0, openUp: false }); +export const DatePicker = React.forwardRef< + HTMLDivElement, + DatePickerProps +>( + ( + { + label, + value, + onChange, + placeholder, + required = false, + maxDate, + className = "", + errorMessage, + }, + ref + ) => { + const dateValue = useMemo( + () => toCalendarDate(value), + [value] + ); - const triggerRef = useRef(null); - const dropdownRef = useRef(null); + const maxDateValue = useMemo(() => { + if (!maxDate) { + return undefined; + } - const calcPos = useCallback(() => { - if (!triggerRef.current) return; - const r = triggerRef.current.getBoundingClientRect(); - const spaceBelow = window.innerHeight - r.bottom; - const openUp = spaceBelow < DROPDOWN_H + 8 && r.top > DROPDOWN_H + 8; - setPos({ - top: openUp ? r.top - 8 : r.bottom + 8, - left: r.left, - width: r.width, - openUp, - }); - }, []); + try { + const yyyy = maxDate.getFullYear(); + const mm = String(maxDate.getMonth() + 1).padStart(2, "0"); + const dd = String(maxDate.getDate()).padStart(2, "0"); - const openPicker = () => { - if (selected) setCursor({ month: selected.getMonth(), year: selected.getFullYear() }); - setView("days"); - calcPos(); - setOpen(true); - }; + return parseInternationalDate(`${yyyy}-${mm}-${dd}`); + } catch { + return undefined; + } + }, [maxDate]); - useEffect(() => { - if (!open) return; - const onScroll = () => calcPos(); - const onResize = () => calcPos(); - const onMouse = (e: MouseEvent) => { - if ( - triggerRef.current?.contains(e.target as Node) || - dropdownRef.current?.contains(e.target as Node) - ) return; - setOpen(false); + const handleChange = (newDate: DateValue | null) => { + if (newDate) { + onChange(toDateString(newDate)); + } else { + onChange(undefined); + } }; - window.addEventListener("scroll", onScroll, true); - window.addEventListener("resize", onResize); - document.addEventListener("mousedown", onMouse); - return () => { - window.removeEventListener("scroll", onScroll, true); - window.removeEventListener("resize", onResize); - document.removeEventListener("mousedown", onMouse); + + const handleClear = () => { + onChange(undefined); }; - }, [open, calcPos]); - const selectDay = (day: number) => { - const d = new Date(cursor.year, cursor.month, day); - const iso = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; - onChange(iso); - setOpen(false); - }; + return ( + + {/* Label */} + {label && ( +