All checks were successful
Build and Deploy / build-and-push (push) Successful in 56s
- 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.
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import React, { useRef } from 'react';
|
|
import { Calendar } from 'lucide-react';
|
|
import { DateRange } from '../types';
|
|
|
|
interface DateRangePickerProps {
|
|
dateRange: DateRange;
|
|
onChange: (range: DateRange) => void;
|
|
}
|
|
|
|
export const DateRangePicker: React.FC<DateRangePickerProps> = ({ dateRange, onChange }) => {
|
|
const startRef = useRef<HTMLInputElement>(null);
|
|
const endRef = useRef<HTMLInputElement>(null);
|
|
|
|
const formatDateForInput = (date: Date) => {
|
|
// 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 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<HTMLInputElement>) => {
|
|
const newStart = parseLocalDate(e.target.value);
|
|
if (newStart && !isNaN(newStart.getTime())) {
|
|
onChange({ ...dateRange, start: newStart });
|
|
}
|
|
};
|
|
|
|
const handleEndChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
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<HTMLInputElement>) => {
|
|
if (ref.current) {
|
|
try {
|
|
if ('showPicker' in HTMLInputElement.prototype) {
|
|
ref.current.showPicker();
|
|
} else {
|
|
ref.current.focus();
|
|
}
|
|
} catch (e) {
|
|
ref.current.focus();
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 bg-white dark:bg-dark-bg border border-zinc-200 dark:border-dark-border px-3 py-2 rounded-lg shadow-sm hover:border-zinc-300 dark:hover:border-dark-border transition-colors">
|
|
<Calendar size={16} className="text-zinc-500 dark:text-dark-muted shrink-0" />
|
|
<div className="flex items-center gap-2 text-sm font-medium text-zinc-700 dark:text-zinc-200">
|
|
|
|
{/* Start Date */}
|
|
<div
|
|
className="relative cursor-pointer hover:text-brand-yellow transition-colors"
|
|
onClick={() => openPicker(startRef)}
|
|
>
|
|
{formatShortDate(dateRange.start)}
|
|
<input
|
|
ref={startRef}
|
|
type="date"
|
|
value={formatDateForInput(dateRange.start)}
|
|
onChange={handleStartChange}
|
|
className="absolute opacity-0 w-0 h-0 overflow-hidden"
|
|
/>
|
|
</div>
|
|
|
|
<span className="text-zinc-400 dark:text-dark-muted font-normal text-xs">até</span>
|
|
|
|
{/* End Date */}
|
|
<div
|
|
className="relative cursor-pointer hover:text-brand-yellow transition-colors"
|
|
onClick={() => openPicker(endRef)}
|
|
>
|
|
{formatShortDate(dateRange.end)}
|
|
<input
|
|
ref={endRef}
|
|
type="date"
|
|
value={formatDateForInput(dateRange.end)}
|
|
onChange={handleEndChange}
|
|
className="absolute opacity-0 w-0 h-0 overflow-hidden"
|
|
/>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |