Spaces:
Sleeping
Sleeping
File size: 3,939 Bytes
d97b8f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import { useState, useRef, useEffect } from "react";
import { format, parse } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { cn } from "@/lib/utils";
interface CustomDateInputProps {
value: string | null;
onChange: (date: string | null) => void;
placeholder?: string;
label?: string;
disabled?: boolean;
minDate?: Date;
maxDate?: Date;
className?: string;
error?: string;
}
export function CustomDateInput({
value,
onChange,
placeholder = "Pick a date",
label,
disabled = false,
minDate,
maxDate,
className,
error
}: CustomDateInputProps) {
const [open, setOpen] = useState(false);
const datePickerRef = useRef<HTMLDivElement>(null);
// Convert string date to Date object for the calendar
const getDateValue = (): Date | undefined => {
if (!value) return undefined;
try {
return parse(value, "yyyy-MM-dd", new Date());
} catch (e) {
console.error("Invalid date format:", value);
return undefined;
}
};
// Update the date value as string in yyyy-MM-dd format
const handleDateSelect = (date: Date | undefined) => {
if (!date) {
onChange(null);
return;
}
try {
const formattedDate = format(date, "yyyy-MM-dd");
onChange(formattedDate);
setOpen(false);
} catch (e) {
console.error("Error formatting date:", e);
}
};
// Format date for display
const getDisplayDate = (): string => {
if (!value) return placeholder;
try {
const date = parse(value, "yyyy-MM-dd", new Date());
return format(date, "PPP"); // Human readable format
} catch (e) {
console.error("Error parsing date for display:", e);
return value;
}
};
// Close date picker when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (datePickerRef.current && !datePickerRef.current.contains(event.target as Node)) {
setOpen(false);
}
};
if (open) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [open]);
const toggleOpen = (e: React.MouseEvent) => {
if (disabled) return;
e.preventDefault();
e.stopPropagation();
setOpen(!open);
};
return (
<div className={cn("w-full space-y-2", className)} ref={datePickerRef}>
{label && <div className="font-medium text-sm">{label}</div>}
<div className="relative w-full">
<div
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background cursor-pointer",
open && "ring-2 ring-ring ring-offset-2",
disabled && "opacity-50 cursor-not-allowed",
!value && "text-muted-foreground"
)}
onClick={toggleOpen}
>
{getDisplayDate()}
<CalendarIcon className="h-4 w-4 opacity-50" />
</div>
{open && (
<div
className="absolute z-[10000] mt-1 w-auto rounded-md border bg-popover p-0 text-popover-foreground shadow-md"
>
<Calendar
mode="single"
selected={getDateValue()}
onSelect={handleDateSelect}
disabled={(date) => {
if (minDate && date < minDate) return true;
if (maxDate && date > maxDate) return true;
return false;
}}
initialFocus
/>
</div>
)}
</div>
{error && <p className="text-sm font-medium text-destructive">{error}</p>}
</div>
);
} |