diff --git a/frontend/src/components/ui/date-picker.tsx b/frontend/src/components/ui/date-picker.tsx index 7997e69..33dce13 100644 --- a/frontend/src/components/ui/date-picker.tsx +++ b/frontend/src/components/ui/date-picker.tsx @@ -34,43 +34,6 @@ function to24Hour(h12: number, ampm: string): number { return isPM ? h12 + 12 : h12; } -/** ISO string → user-friendly display: DD/MM/YYYY or DD/MM/YYYY hh:mm AM */ -function isoToDisplay(iso: string, mode: 'date' | 'datetime'): string { - if (!iso) return ''; - const parts = iso.split('T'); - const dp = parts[0]?.split('-'); - if (!dp || dp.length !== 3) return ''; - const dateStr = `${dp[2]}/${dp[1]}/${dp[0]}`; - if (mode === 'date') return dateStr; - const tp = parts[1]?.split(':'); - if (!tp || tp.length < 2) return dateStr; - const h = parseInt(tp[0], 10); - if (isNaN(h)) return dateStr; - const { hour: h12, ampm } = to12Hour(h); - return `${dateStr} ${pad(h12)}:${tp[1].slice(0, 2)} ${ampm}`; -} - -/** User-friendly display → ISO string, or null if incomplete/invalid */ -function displayToIso(display: string, mode: 'date' | 'datetime'): string | null { - if (!display) return null; - if (mode === 'date') { - const m = display.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); - if (!m) return null; - const d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10); - if (mo < 1 || mo > 12 || d < 1 || d > getDaysInMonth(y, mo - 1)) return null; - return `${m[3]}-${pad(mo)}-${pad(d)}`; - } - const m = display.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2})\s*(AM|PM)$/i); - if (!m) return null; - const d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10); - if (mo < 1 || mo > 12 || d < 1 || d > getDaysInMonth(y, mo - 1)) return null; - let h = parseInt(m[4], 10); - const min = parseInt(m[5], 10); - if (h < 1 || h > 12 || min < 0 || min > 59) return null; - h = to24Hour(h, m[6]); - return `${m[3]}-${pad(mo)}-${pad(d)}T${pad(h)}:${pad(min)}`; -} - const MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', @@ -152,12 +115,6 @@ const DatePicker = React.forwardRef( const [hour, setHour] = React.useState(parsed?.hour ?? 0); const [minute, setMinute] = React.useState(parsed?.minute ?? 0); - // Input variant: user-friendly display string (DD/MM/YYYY or DD/MM/YYYY hh:mm AM) - const [displayValue, setDisplayValue] = React.useState(() => - variant === 'input' ? isoToDisplay(value, mode) : '' - ); - const isInternalChange = React.useRef(false); - // Refs const triggerRef = React.useRef(null); const wrapperRef = React.useRef(null); @@ -182,18 +139,6 @@ const DatePicker = React.forwardRef( // eslint-disable-next-line react-hooks/exhaustive-deps }, [value, open]); - // Sync display string from ISO value (input variant). - // Skips when the change originated from the text input to avoid - // overwriting the user's in-progress typing. - React.useEffect(() => { - if (variant !== 'input') return; - if (isInternalChange.current) { - isInternalChange.current = false; - return; - } - setDisplayValue(value ? isoToDisplay(value, mode) : ''); - }, [value, mode, variant]); - // Position popup const updatePosition = React.useCallback(() => { const el = variant === 'input' ? wrapperRef.current : triggerRef.current; @@ -484,32 +429,19 @@ const DatePicker = React.forwardRef( ) : null; - // ── Input variant: typeable input + calendar icon trigger ── + // ── Input variant: native date/datetime-local + calendar icon for custom popup ── if (variant === 'input') { return ( <> -
+
{ - const typed = e.target.value; - setDisplayValue(typed); - if (typed === '') { - isInternalChange.current = true; - onChange(''); - return; - } - const iso = displayToIso(typed, mode); - if (iso) { - isInternalChange.current = true; - onChange(iso); - } - }} + value={value} + onChange={(e) => onChange(e.target.value)} onBlur={handleInputBlur} onKeyDown={(e) => { if (open && e.key === 'Enter') { @@ -520,7 +452,8 @@ const DatePicker = React.forwardRef( }} required={required} disabled={disabled} - placeholder={placeholder || (mode === 'datetime' ? 'DD/MM/YYYY hh:mm AM' : 'DD/MM/YYYY')} + min={min} + max={max} className={cn( 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-9 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className diff --git a/frontend/src/index.css b/frontend/src/index.css index 9dfb4e7..e819ade 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -200,6 +200,11 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator { cursor: pointer; } +/* Hide native picker icon inside DatePicker wrapper (custom icon replaces it) */ +.datepicker-wrapper input::-webkit-calendar-picker-indicator { + display: none; +} + /* ── Form validation — red outline only after submit attempt ── */ form[data-submitted] input:invalid, form[data-submitted] select:invalid,