pmtool / src /components /ui /custom-date-input.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
3.94 kB
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>
);
}