From f884f6dc3ce380b2fad6fe14397dbde754206d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Tue, 17 Mar 2026 14:12:20 -0300 Subject: [PATCH] fix: resolve date range picker timezone offset bug - Fixed a bug where selecting a date in the native date picker resulted in the previous day being selected due to the browser converting the 'YYYY-MM-DD' string to UTC midnight and then shifting it back to local time (e.g. UTC-3 in Brazil). - Explicitly parsed the date string and constructed the Date object using local time coordinates to ensure visual and data consistency. --- components/DateRangePicker.tsx | 101 +++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/components/DateRangePicker.tsx b/components/DateRangePicker.tsx index e495764..677ecbf 100644 --- a/components/DateRangePicker.tsx +++ b/components/DateRangePicker.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useRef } from 'react'; import { Calendar } from 'lucide-react'; import { DateRange } from '../types'; @@ -8,54 +8,95 @@ interface DateRangePickerProps { } export const DateRangePicker: React.FC = ({ dateRange, onChange }) => { - const [startType, setStartType] = useState<'text' | 'date'>('text'); - const [endType, setEndType] = useState<'text' | 'date'>('text'); + const startRef = useRef(null); + const endRef = useRef(null); const formatDateForInput = (date: Date) => { - return date.toISOString().split('T')[0]; // YYYY-MM-DD for + // Format to local YYYY-MM-DD to avoid timezone shifts + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; }; - const formatDateForDisplay = (date: Date) => { - return date.toLocaleDateString('pt-BR'); // DD/MM/YYYY for visual text + const formatShortDate = (date: Date) => { + return date.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', timeZone: 'America/Sao_Paulo' }); + }; + + const parseLocalDate = (value: string) => { + // Split "YYYY-MM-DD" and create date in local timezone to avoid UTC midnight shift + if (!value) return null; + const [year, month, day] = value.split('-'); + return new Date(parseInt(year), parseInt(month) - 1, parseInt(day)); }; const handleStartChange = (e: React.ChangeEvent) => { - const newStart = new Date(e.target.value); - if (!isNaN(newStart.getTime())) { + const newStart = parseLocalDate(e.target.value); + if (newStart && !isNaN(newStart.getTime())) { onChange({ ...dateRange, start: newStart }); } }; const handleEndChange = (e: React.ChangeEvent) => { - const newEnd = new Date(e.target.value); - if (!isNaN(newEnd.getTime())) { + const newEnd = parseLocalDate(e.target.value); + if (newEnd && !isNaN(newEnd.getTime())) { + // Set to end of day to ensure the query includes the whole day + newEnd.setHours(23, 59, 59, 999); onChange({ ...dateRange, end: newEnd }); } }; + const openPicker = (ref: React.RefObject) => { + if (ref.current) { + try { + if ('showPicker' in HTMLInputElement.prototype) { + ref.current.showPicker(); + } else { + ref.current.focus(); + } + } catch (e) { + ref.current.focus(); + } + } + }; + return (
-
- setStartType('date')} - onBlur={() => setStartType('text')} - onChange={handleStartChange} - lang="pt-BR" - className="bg-transparent text-zinc-700 dark:text-zinc-200 font-medium outline-none cursor-pointer w-24 sm:w-28 md:w-auto" - /> - até - setEndType('date')} - onBlur={() => setEndType('text')} - onChange={handleEndChange} - lang="pt-BR" - className="bg-transparent text-zinc-700 dark:text-zinc-200 font-medium outline-none cursor-pointer w-24 sm:w-28 md:w-auto" - /> +
+ + {/* Start Date */} +
openPicker(startRef)} + > + {formatShortDate(dateRange.start)} + +
+ + até + + {/* End Date */} +
openPicker(endRef)} + > + {formatShortDate(dateRange.end)} + +
+
);