llzai's picture
Upload 1793 files
9853396 verified
import * as React from 'react';
import { Calendar } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { cn } from '@/lib/utils';
import { normalizeDateTimeRangeValue, type DateTimeRangeValue } from '@/utils/date-range';
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { DateTimeRangePicker } from './date-time-range-picker';
import { formatRange } from './utils';
export type { DateTimeRangeValue, TimeValue } from '@/utils/date-range'
export { DateTimeRangePicker } from './date-time-range-picker'
interface DateRangePickerProps {
value?: DateTimeRangeValue
onChange?: (range: DateTimeRangeValue | undefined) => void
onCancel?: () => void
onConfirm?: (range: DateTimeRangeValue) => void
className?: string
}
export function DateRangePicker(props: DateRangePickerProps) {
const { value, onChange, onCancel, onConfirm, className } = props
const { t } = useTranslation()
const isControlled = Object.prototype.hasOwnProperty.call(props, 'value')
const [open, setOpen] = React.useState(false)
const normalizedValue = React.useMemo(
() => (value ? normalizeDateTimeRangeValue(value) : undefined),
[value]
)
const [internalValue, setInternalValue] = React.useState<DateTimeRangeValue | undefined>(
normalizedValue
)
const [pendingValue, setPendingValue] = React.useState<DateTimeRangeValue | undefined>(
normalizedValue
)
React.useEffect(() => {
if (!isControlled) return
setInternalValue(normalizedValue)
}, [isControlled, normalizedValue])
React.useEffect(() => {
if (open) {
const currentValue = isControlled ? normalizedValue : internalValue
setPendingValue(currentValue)
}
}, [open, isControlled, normalizedValue, internalValue])
const handlePendingChange = React.useCallback(
(next: DateTimeRangeValue | undefined) => {
const nextValue = next ? normalizeDateTimeRangeValue(next) : undefined
setPendingValue(nextValue)
},
[]
)
const handleConfirm = React.useCallback(
(next: DateTimeRangeValue) => {
const nextValue = normalizeDateTimeRangeValue(next)
const hasRange = !!nextValue.from || !!nextValue.to
if (!hasRange) {
if (!isControlled) setInternalValue(undefined)
onChange?.(undefined)
setOpen(false)
return
}
if (!isControlled) setInternalValue(nextValue)
onChange?.(nextValue)
onConfirm?.(nextValue)
setOpen(false)
},
[isControlled, onChange, onConfirm]
)
const handleCancel = React.useCallback(() => {
const currentValue = isControlled ? normalizedValue : internalValue
setPendingValue(currentValue)
setOpen(false)
onCancel?.()
}, [isControlled, normalizedValue, internalValue, onCancel])
const currentValue = isControlled ? normalizedValue : internalValue
const label = formatRange(currentValue?.from, currentValue?.to, t('common.filters.dateRange'))
return (
<div className={cn('grid gap-2', className)}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id='date'
variant='outline'
size='sm'
className={cn(
'h-8 border-solid',
!currentValue?.from && !currentValue?.to && 'text-muted-foreground'
)}
>
<Calendar className='h-4 w-4' />
<span>{label}</span>
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto border-none bg-transparent p-0 shadow-none' align='start'>
<DateTimeRangePicker
value={pendingValue}
onChange={handlePendingChange}
onCancel={handleCancel}
onConfirm={handleConfirm}
/>
</PopoverContent>
</Popover>
</div>
)
}