diff options
| author | Fuwn <[email protected]> | 2026-01-24 13:09:50 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-24 13:09:50 +0000 |
| commit | 396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b (patch) | |
| tree | b9df4ca6a70db45cfffbae6fdd7252e20fb8e93c /src/components/input/DateFilter.tsx | |
| download | umami-main.tar.xz umami-main.zip | |
Created from https://vercel.com/new
Diffstat (limited to 'src/components/input/DateFilter.tsx')
| -rw-r--r-- | src/components/input/DateFilter.tsx | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx new file mode 100644 index 0000000..2e17529 --- /dev/null +++ b/src/components/input/DateFilter.tsx @@ -0,0 +1,141 @@ +import { Dialog, ListItem, ListSeparator, Modal, Select, type SelectProps } from '@umami/react-zen'; +import { endOfYear } from 'date-fns'; +import { Fragment, type Key, useState } from 'react'; +import { DateDisplay } from '@/components/common/DateDisplay'; +import { useMessages, useMobile } from '@/components/hooks'; +import { DatePickerForm } from '@/components/metrics/DatePickerForm'; +import { parseDateRange } from '@/lib/date'; + +export interface DateFilterProps extends SelectProps { + value?: string; + onChange?: (value: string) => void; + showAllTime?: boolean; + renderDate?: boolean; + placement?: any; +} + +export function DateFilter({ + value, + onChange, + showAllTime, + renderDate, + placement = 'bottom', + ...props +}: DateFilterProps) { + const { formatMessage, labels } = useMessages(); + const [showPicker, setShowPicker] = useState(false); + const { startDate, endDate } = parseDateRange(value) || {}; + const { isMobile } = useMobile(); + + const options = [ + { label: formatMessage(labels.today), value: '0day' }, + { + label: formatMessage(labels.lastHours, { x: '24' }), + value: '24hour', + }, + { + label: formatMessage(labels.thisWeek), + value: '0week', + divider: true, + }, + { + label: formatMessage(labels.lastDays, { x: '7' }), + value: '7day', + }, + { + label: formatMessage(labels.thisMonth), + value: '0month', + divider: true, + }, + { + label: formatMessage(labels.lastDays, { x: '30' }), + value: '30day', + }, + { + label: formatMessage(labels.lastDays, { x: '90' }), + value: '90day', + }, + { label: formatMessage(labels.thisYear), value: '0year' }, + { + label: formatMessage(labels.lastMonths, { x: '6' }), + value: '6month', + divider: true, + }, + { + label: formatMessage(labels.lastMonths, { x: '12' }), + value: '12month', + }, + showAllTime && { + label: formatMessage(labels.allTime), + value: 'all', + divider: true, + }, + { + label: formatMessage(labels.customRange), + value: 'custom', + divider: true, + }, + ] + .filter(n => n) + .map((a, id) => ({ ...a, id })); + + const handleChange = (value: Key) => { + if (value === 'custom') { + setShowPicker(true); + return; + } + onChange(value.toString()); + }; + + const handlePickerChange = (value: string) => { + setShowPicker(false); + onChange(value.toString()); + }; + + const renderValue = ({ defaultChildren }) => { + return value?.startsWith('range') || renderDate ? ( + <DateDisplay startDate={startDate} endDate={endDate} /> + ) : ( + defaultChildren + ); + }; + + const selectedValue = value.endsWith(':all') ? 'all' : value; + + return ( + <> + <Select + {...props} + value={selectedValue} + placeholder={formatMessage(labels.selectDate)} + onChange={handleChange} + renderValue={renderValue} + popoverProps={{ placement }} + isFullscreen={isMobile} + > + {options.map(({ label, value, divider }: any) => { + return ( + <Fragment key={label}> + {divider && <ListSeparator />} + <ListItem id={value}>{label}</ListItem> + </Fragment> + ); + })} + </Select> + {showPicker && ( + <Modal isOpen={true}> + <Dialog> + <DatePickerForm + startDate={startDate} + endDate={endDate} + minDate={new Date(2000, 0, 1)} + maxDate={endOfYear(new Date())} + onChange={handlePickerChange} + onClose={() => setShowPicker(false)} + /> + </Dialog> + </Modal> + )} + </> + ); +} |