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