import React, { useState, useRef, useEffect, useCallback } from 'react'; export interface TimeRange { label: string; from: string; // ISO string to?: string; // ISO string (defaults to now) range?: string; // shorthand for API (e.g., '1h', '24h', '7d') granularity: string; // 'minute' | 'hour' | 'day' } const PRESETS: Array<{ label: string; rangeName: string; offsetMs: number; granularity: string }> = [ { label: 'Last 1 hour', rangeName: '1h', offsetMs: 3_600_000, granularity: 'hour' }, { label: 'Last 6 hours', rangeName: '6h', offsetMs: 21_600_000, granularity: 'hour' }, { label: 'Last 24 hours', rangeName: '24h', offsetMs: 86_400_000, granularity: 'hour' }, { label: 'Last 3 days', rangeName: '3d', offsetMs: 259_200_000, granularity: 'hour' }, { label: 'Last 7 days', rangeName: '7d', offsetMs: 604_800_000, granularity: 'day' }, { label: 'Last 30 days', rangeName: '30d', offsetMs: 2_592_000_000, granularity: 'day' }, ]; function presetToTimeRange(preset: (typeof PRESETS)[number]): TimeRange { const now = new Date(); return { label: preset.label, from: new Date(now.getTime() - preset.offsetMs).toISOString(), to: now.toISOString(), range: preset.rangeName, granularity: preset.granularity, }; } export const DEFAULT_TIME_RANGE: TimeRange = presetToTimeRange(PRESETS[2]); // Last 24 hours interface TimeRangePickerProps { value: TimeRange; onChange: (range: TimeRange) => void; } export function TimeRangePicker({ value, onChange }: TimeRangePickerProps): React.ReactElement { const [open, setOpen] = useState(false); const [showCustom, setShowCustom] = useState(false); const [customFrom, setCustomFrom] = useState(''); const [customTo, setCustomTo] = useState(''); const ref = useRef(null); // Close on outside click useEffect(() => { function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false); setShowCustom(false); } } if (open) document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [open]); const handlePreset = useCallback((preset: (typeof PRESETS)[number]) => { onChange(presetToTimeRange(preset)); setOpen(false); setShowCustom(false); }, [onChange]); const handleCustomApply = useCallback(() => { if (!customFrom) return; const fromDate = new Date(customFrom); const toDate = customTo ? new Date(customTo) : new Date(); const diffMs = toDate.getTime() - fromDate.getTime(); const granularity = diffMs <= 86_400_000 ? 'hour' : 'day'; onChange({ label: `${fromDate.toLocaleDateString()} – ${toDate.toLocaleDateString()}`, from: fromDate.toISOString(), to: toDate.toISOString(), granularity, }); setOpen(false); setShowCustom(false); }, [customFrom, customTo, onChange]); return (
{open && (
{!showCustom ? (
{PRESETS.map((p) => ( ))}
) : (
setCustomFrom(e.target.value)} className="w-full rounded border border-gray-300 px-2 py-1 text-sm" />
setCustomTo(e.target.value)} className="w-full rounded border border-gray-300 px-2 py-1 text-sm" />
)}
)}
); }