Skip to main content
Glama
date-time-picker-range.tsx10.8 kB
import { format, subDays, addDays, startOfDay, endOfDay } from 'date-fns'; import { t } from 'i18next'; import { Calendar as CalendarIcon, Clock } from 'lucide-react'; import * as React from 'react'; import { DateRange } from 'react-day-picker'; import { Button } from '@/components/ui/button'; import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { cn } from '@/lib/utils'; import { Separator } from './separator'; import { TimePicker } from './time-picker'; export type PresetKey = | '7days' | '14days' | '30days' | '90days' | '7' | '14' | '30' | '90'; type DateTimePickerWithRangeProps = { onChange: (date: DateRange | undefined) => void; className?: string; from?: string; to?: string; maxDate?: Date; minDate?: Date; presetType: 'past' | 'future'; defaultSelectedRange?: PresetKey; }; const applyTimeToDate = (timeDate: Date, targetDate: Date): Date => { const d = new Date(targetDate); d.setHours( timeDate.getHours(), timeDate.getMinutes(), timeDate.getSeconds(), timeDate.getMilliseconds(), ); return d; }; const getDayBoundaries = () => { const now = new Date(); return { from: startOfDay(now), to: endOfDay(now), }; }; const PRESETS: Record<PresetKey, () => { from: Date; to: Date }> = { '7days': () => ({ from: subDays(new Date(), 7), to: new Date() }), '14days': () => ({ from: subDays(new Date(), 14), to: new Date() }), '30days': () => ({ from: subDays(new Date(), 30), to: new Date() }), '90days': () => ({ from: subDays(new Date(), 90), to: new Date() }), '7': () => ({ from: new Date(), to: addDays(new Date(), 7) }), '14': () => ({ from: new Date(), to: addDays(new Date(), 14) }), '30': () => ({ from: new Date(), to: addDays(new Date(), 30) }), '90': () => ({ from: new Date(), to: addDays(new Date(), 90) }), }; const getPresetLabel = (value: string) => { const labels: Record<string, string> = { '7days': t('Last 7 Days'), '14days': t('Last 14 Days'), '30days': t('Last 30 Days'), '90days': t('Last 90 Days'), '7': t('Next 7 days'), '14': t('Next 14 days'), '30': t('Next 30 days'), '90': t('Next 90 days'), }; return labels[value] || ''; }; const detectPreset = ( from?: Date, to?: Date, presetType?: 'past' | 'future', ): string | null => { if (!from || !to) return null; const candidates = presetType === 'past' ? (['7days', '14days', '30days', '90days'] as PresetKey[]) : (['7', '14', '30', '90'] as PresetKey[]); for (const key of candidates) { const { from: pf, to: pt } = PRESETS[key](); if ( startOfDay(pf).getTime() === startOfDay(from).getTime() && endOfDay(pt).getTime() === endOfDay(to).getTime() ) { return key; } } return null; }; const getDefaultRange = (presetKey: PresetKey) => { const preset = PRESETS[presetKey](); preset.from!.setHours(0, 0, 0, 0); preset.to!.setHours(23, 59, 59, 999); return preset; }; const getInitialDateAndPreset = ( fromProp?: string, toProp?: string, presetType: 'past' | 'future' = 'past', defaultPresetKey?: PresetKey, ): { initialDate: DateRange | undefined; initialPreset: string | null } => { let initialDate: DateRange | undefined; let initialPreset: string | null = null; if (fromProp && toProp) { initialDate = { from: new Date(fromProp), to: new Date(toProp), }; initialPreset = detectPreset(initialDate.from, initialDate.to, presetType); } else if (defaultPresetKey) { initialDate = getDefaultRange(defaultPresetKey); initialPreset = defaultPresetKey; } return { initialDate, initialPreset }; }; export function DateTimePickerWithRange({ className, onChange, from, to, maxDate = new Date(), minDate, presetType = 'past', defaultSelectedRange, }: DateTimePickerWithRangeProps) { const { initialDate, initialPreset } = React.useMemo(() => { return getInitialDateAndPreset(from, to, presetType, defaultSelectedRange); }, [from, to, presetType, defaultSelectedRange]); const [date, setDate] = React.useState<DateRange | undefined>(initialDate); const [timeDate, setTimeDate] = React.useState<DateRange>({ from: initialDate?.from, to: initialDate?.to, }); const [selectedPreset, setSelectedPreset] = React.useState<string | null>( initialPreset, ); const isDefaultApplied = React.useRef(!!initialPreset && !from && !to); React.useEffect(() => { if (isDefaultApplied.current && date) { onChange(date); isDefaultApplied.current = false; } }, [date, onChange]); React.useEffect(() => { if (from && to) { const newDate: DateRange = { from: new Date(from), to: new Date(to) }; setDate(newDate); setTimeDate({ from: newDate.from, to: newDate.to }); const preset = detectPreset(newDate.from, newDate.to, presetType); setSelectedPreset(preset); } else if (!from && !to) { setDate(initialDate); setTimeDate({ from: initialDate?.from, to: initialDate?.to }); setSelectedPreset(initialPreset); } }, [from, to, presetType, initialDate, initialPreset]); const handleSelect = (selectedDate: DateRange | undefined) => { setSelectedPreset(null); if (!selectedDate) { setDate(undefined); onChange(undefined); return; } const newDate = { from: selectedDate.from ? applyTimeToDate( timeDate.from || getDayBoundaries().from, selectedDate.from, ) : undefined, to: selectedDate.to ? applyTimeToDate(timeDate.to || getDayBoundaries().to, selectedDate.to) : undefined, }; setDate(newDate); onChange(newDate); }; const handlePresetChange = (value: string) => { const newRange = PRESETS[value as PresetKey](); newRange.from!.setHours(0, 0, 0, 0); newRange.to!.setHours(23, 59, 59, 999); setDate(newRange); setTimeDate({ from: newRange.from, to: newRange.to }); setSelectedPreset(value); onChange(newRange); }; return ( <div className={cn('grid gap-2', className)}> <Popover> <PopoverTrigger asChild> <Button variant="outline" className={cn( 'min-w-[90px] border-dashed justify-start text-left font-normal', )} > <CalendarIcon className="mr-2 h-4 w-4" /> {selectedPreset ? ( <span>{getPresetLabel(selectedPreset)}</span> ) : date?.from ? ( date.to ? ( <div className="flex gap-2 items-center"> <div>{format(date.from, 'LLL dd, y, hh:mm a')}</div> <div>{t('to')}</div> <div>{format(date.to, 'LLL dd, y, hh:mm a')}</div> </div> ) : ( format(date.from, 'LLL dd, y, hh:mm a') ) ) : ( <span>{t('Pick a date range')}</span> )} </Button> </PopoverTrigger> <PopoverContent className="w-auto p-2" align="start"> <div className="flex space-x-2 mb-2"> <Select onValueChange={handlePresetChange} value={selectedPreset || undefined} > <SelectTrigger> <SelectValue placeholder={t('Select preset')} /> </SelectTrigger> <SelectContent> {presetType === 'past' ? ( <> <SelectItem value="7days">{t('Last 7 Days')}</SelectItem> <SelectItem value="14days">{t('Last 14 Days')}</SelectItem> <SelectItem value="30days">{t('Last 30 Days')}</SelectItem> <SelectItem value="90days">{t('Last 90 Days')}</SelectItem> </> ) : ( <> <SelectItem value="7">{t('Next 7 days')}</SelectItem> <SelectItem value="14">{t('Next 14 days')}</SelectItem> <SelectItem value="30">{t('Next 30 days')}</SelectItem> <SelectItem value="90">{t('Next 90 days')}</SelectItem> </> )} </SelectContent> </Select> </div> <Calendar initialFocus mode="range" defaultMonth={date?.from} selected={date} onSelect={handleSelect} numberOfMonths={2} weekStartsOn={1} toDate={maxDate} fromDate={minDate} /> <Separator className="mb-4" /> <div className="flex gap-1.5 px-2 items-center text-sm mb-3"> <Clock className="w-4 h-4 text-muted-foreground" /> {t('Select Time Range')} </div> <div className="flex gap-3 items-center px-2 mb-2"> <TimePicker date={timeDate.from} name="from" setDate={(fromTime) => { const fromWithTime = applyTimeToDate( fromTime, date?.from ?? new Date(), ); const updated = { from: fromWithTime, to: date?.to }; setDate(updated); setTimeDate({ ...timeDate, from: fromTime }); setSelectedPreset(null); onChange(updated); }} /> {t('to')} <TimePicker date={timeDate.to} name="to" setDate={(toTime) => { const toWithTime = applyTimeToDate( toTime, date?.to ?? date?.from ?? new Date(), ); const updated = { from: date?.from, to: toWithTime }; setDate(updated); setTimeDate({ ...timeDate, to: toTime }); setSelectedPreset(null); onChange(updated); }} /> </div> <div className="flex justify-center mt-3"> <Button variant="ghost" size="sm" className="text-primary hover:!text-primary w-full" onClick={() => { setDate(undefined); setTimeDate({ from: undefined, to: undefined }); setSelectedPreset(null); onChange(undefined); }} > {t('Clear')} </Button> </div> </PopoverContent> </Popover> </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server